Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
roawh opened this issue Oct 12, 2023 · 2 comments
Closed
Milestone

Comments

@roawh
Copy link

roawh commented Oct 12, 2023

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 added a commit that referenced this issue Oct 15, 2023
@DevCharly
Copy link
Collaborator

Thanks for reporting and the great test case 👍

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

@DevCharly DevCharly added this to the 3.2.2 milestone Oct 15, 2023
@DevCharly
Copy link
Collaborator

Fixed in FlatLaf 3.2.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants