JFormDesigner / FlatLaf

FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
https://www.formdev.com/flatlaf/
Apache License 2.0
3.4k stars 270 forks source link

Null Pointer Exception when using JTextField as JTabbedPane header component #745

Closed roawh closed 1 year ago

roawh commented 1 year ago

Flatlaf Version: 3.2.1 (also tested with 3.1.1)

Setup: Application has a JTabbedPane with custom Tab Header components. The custom header is a JPanel that includes a JTextField. Application has a theme toggle with FlatLAF and system themes.

Apparent Bug: If the component part of the tab header (i.e., the JTextField) is clicked before switching between Light and Dark Flat themes, a Null Pointer Exception is thrown.

Steps to Reproduce:

  1. Set theme to light or dark flat
  2. Click on the tab header component (i.e., the Text Field). Note: error is not thrown if clicking in the tab area that is NOT part of the component.
  3. Switch theme to light or dark flat (whatever is not currently selected) -- NPE is thrown

Code sample that produces the error: FlatLaf_NPE_Tabbed_Pane_Example.txt

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package flattabtest;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

/**
 * Displays a TabbedPane with no content with a menu that can switch between
 * different L&Fs. The tabbed pane header is a custom JPanel with a JTextField.
 * 
 * Causes a Null Pointer Exception when doing the following:
 * 1) Start with or switch to Dark/Light Flat Theme
 * 2) Click on Tab Header -- in the COMPONENT (JTextField) area
 * 3) Switch to Light/Dark Flat Theme
 */
public class FlatTabTest {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame app = new JFrame();
                JPanel appPanel = new JPanel(new BorderLayout());
                JTabbedPane tabbedPane = new JTabbedPane();
                JMenuBar menuBar = new JMenuBar();
                JMenu menu = new JMenu("Theme");
                JRadioButtonMenuItem v_noThemeMenuItem = new JRadioButtonMenuItem("Default Theme", true);
                v_noThemeMenuItem.addActionListener(new ThemeActionListener(UIManager.getCrossPlatformLookAndFeelClassName()));
                JRadioButtonMenuItem v_defaultThemeMenuItem = new JRadioButtonMenuItem("System Theme", false);
                v_defaultThemeMenuItem.addActionListener(new ThemeActionListener(UIManager.getSystemLookAndFeelClassName()));
                JRadioButtonMenuItem v_lightFlatLafMenuItem = new JRadioButtonMenuItem("Light Flat Theme", false);
                v_lightFlatLafMenuItem.addActionListener(new ThemeActionListener("com.formdev.flatlaf.FlatLightLaf"));
                JRadioButtonMenuItem v_darkFlatLafMenuItem = new JRadioButtonMenuItem("Dark Flat Theme", false);
                v_darkFlatLafMenuItem.addActionListener(new ThemeActionListener("com.formdev.flatlaf.FlatDarkLaf"));
                menu.add(v_noThemeMenuItem);
                menu.add(v_defaultThemeMenuItem);
                menu.add(v_lightFlatLafMenuItem);
                menu.add(v_darkFlatLafMenuItem);
                ButtonGroup themeButtonGroup = new ButtonGroup();
                themeButtonGroup.add(v_noThemeMenuItem);
                themeButtonGroup.add(v_defaultThemeMenuItem);
                themeButtonGroup.add(v_lightFlatLafMenuItem);
                themeButtonGroup.add(v_darkFlatLafMenuItem);
                menuBar.add(menu);                
                tabbedPane.add("TEST TAB", new JPanel());
                tabbedPane.setTabComponentAt(0, new CustomHeader("TEST TAB"));
                appPanel.add(tabbedPane, BorderLayout.CENTER);
                appPanel.add(menuBar, BorderLayout.NORTH);
                app.setContentPane(appPanel);
                app.setSize(new Dimension(500, 500));
                app.setLocationRelativeTo(null);
                app.setVisible(true);
            }
        });
    }

    public static void themeChanged(String theme) {
        if (UIManager.getLookAndFeel().getClass().getName().equals(theme)) {
            return;
        }
        try {
            UIManager.setLookAndFeel(theme);
            updateLAFRecursively();
            Logger.getLogger(FlatTabTest.class.getName()).log(Level.INFO, "Set Look and Feel {0}", theme);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
            Logger.getLogger(FlatTabTest.class.getName()).log(Level.SEVERE, e.toString(), e);
        }
    }

    /**
     * Updates the component tree UI for all frames and their children.
     */
    private static void updateLAFRecursively() {
        for (Window window : Window.getWindows()) {
            updateLAFRecursively(window);
        }
    }

    /**
     * Updates the component tree UI for all windows owned by the given window.
     *
     * @param window
     */
    private static void updateLAFRecursively(Window window) {
        for (Window childWindow : window.getOwnedWindows()) {
            updateLAFRecursively(childWindow);
        }
        SwingUtilities.updateComponentTreeUI(window);
    }

    static class CustomHeader extends JPanel {

        private JTextField label;

        public CustomHeader(String title) {
            setLayout(new BorderLayout());
            label = new JTextField(title);
            add(label, BorderLayout.CENTER);
        }
    }

    static class ThemeActionListener implements ActionListener {

        private String theme;

        public ThemeActionListener(String theme) {
            this.theme = theme;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            themeChanged(theme);
        }
    }
}

Stack trace:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at javax.swing.plaf.basic.BasicTabbedPaneUI.calculateTabHeight(BasicTabbedPaneUI.java:1736)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI.calculateTabHeight(FlatTabbedPaneUI.java:943)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.calculateMaxTabHeight(BasicTabbedPaneUI.java:1746)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI.calculateMaxTabHeight(FlatTabbedPaneUI.java:955)
    at javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout.calculateTabRects(BasicTabbedPaneUI.java:2590)
    at javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout.calculateLayoutInfo(BasicTabbedPaneUI.java:2516)
    at javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout.layoutContainer(BasicTabbedPaneUI.java:2411)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI$FlatTabbedPaneLayout.layoutContainer(FlatTabbedPaneUI.java:3012)
    at java.awt.Container.layout(Container.java:1513)
    at java.awt.Container.doLayout(Container.java:1502)
    at java.awt.Container.validateTree(Container.java:1698)
    at java.awt.Container.validate(Container.java:1633)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.ensureCurrentLayout(BasicTabbedPaneUI.java:1451)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.getTabBounds(BasicTabbedPaneUI.java:1471)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI.repaintTab(FlatTabbedPaneUI.java:834)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI.access$6400(FlatTabbedPaneUI.java:181)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI$FlatSelectedTabRepainter.repaintSelectedTab(FlatTabbedPaneUI.java:3636)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI$FlatSelectedTabRepainter.repaintSelectedTabs(FlatTabbedPaneUI.java:3630)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI$FlatSelectedTabRepainter.propertyChange(FlatTabbedPaneUI.java:3614)
    at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:327)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
    at java.awt.KeyboardFocusManager.firePropertyChange(KeyboardFocusManager.java:1493)
    at java.awt.KeyboardFocusManager.setGlobalPermanentFocusOwner(KeyboardFocusManager.java:780)
    at java.awt.Component.removeNotify(Component.java:6999)
    at java.awt.Container.removeNotify(Container.java:2823)
    at javax.swing.JComponent.removeNotify(JComponent.java:4761)
    at javax.swing.text.JTextComponent.removeNotify(JTextComponent.java:1602)
    at java.awt.Container.removeNotify(Container.java:2807)
    at javax.swing.JComponent.removeNotify(JComponent.java:4761)
    at java.awt.Container.removeAll(Container.java:1300)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.uninstallTabContainer(BasicTabbedPaneUI.java:348)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.uninstallComponents(BasicTabbedPaneUI.java:332)
    at com.formdev.flatlaf.ui.FlatTabbedPaneUI.uninstallComponents(FlatTabbedPaneUI.java:465)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.uninstallUI(BasicTabbedPaneUI.java:233)
    at javax.swing.JComponent.uninstallUIAndProperties(JComponent.java:675)
    at javax.swing.JComponent.setUI(JComponent.java:652)
    at javax.swing.JTabbedPane.setUI(JTabbedPane.java:231)
    at javax.swing.JTabbedPane.updateUI(JTabbedPane.java:247)
    at javax.swing.SwingUtilities.updateComponentTreeUI0(SwingUtilities.java:1238)
    at javax.swing.SwingUtilities.updateComponentTreeUI0(SwingUtilities.java:1253)
    at javax.swing.SwingUtilities.updateComponentTreeUI0(SwingUtilities.java:1253)
    at javax.swing.SwingUtilities.updateComponentTreeUI0(SwingUtilities.java:1253)
    at javax.swing.SwingUtilities.updateComponentTreeUI0(SwingUtilities.java:1253)
    at javax.swing.SwingUtilities.updateComponentTreeUI(SwingUtilities.java:1229)
DevCharly commented 1 year ago

Thanks for reporting and the great test case 👍

fixed in latest 3.3-SNAPSHOT: https://github.com/JFormDesigner/FlatLaf#snapshots

DevCharly commented 1 year ago

Fixed in FlatLaf 3.2.2