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

ArrayIndexOutOfBoundsException in FlatTabbedPaneUI #875

Closed
IanKrL opened this issue Aug 1, 2024 · 4 comments
Closed

ArrayIndexOutOfBoundsException in FlatTabbedPaneUI #875

IanKrL opened this issue Aug 1, 2024 · 4 comments
Milestone

Comments

@IanKrL
Copy link

IanKrL commented Aug 1, 2024

When a TabbedPane returns a selected index of -1, it causes a ArrayIndexOutOfBoundsException.

Linux: AlmaLinux 8.10
Gnome: Version 3.32.2

package stuffs;

import java.net.UnknownHostException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SingleSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;

import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLightLaf;

public class SimpleLaf {
    private static final String TEXT = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG";

    public static void main(String[] args) throws UnknownHostException {
        SwingUtilities.invokeLater(() -> {
            JFrame.setDefaultLookAndFeelDecorated(true);
            FlatLightLaf.setup();
            final JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setTitle("Some Title");
            frame.setSize(350, 100);
            final JPanel panel = new JPanel();

            JTabbedPane pane = new JTabbedPane();
            pane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_TYPE,
                FlatClientProperties.TABBED_PANE_TAB_TYPE_CARD);
            SingleSelectionModel model = new SingleSelectionModel() {

                @Override
                public void setSelectedIndex(int index) {}

                @Override
                public void removeChangeListener(ChangeListener listener) {}

                @Override
                public boolean isSelected() {
                    return false;
                }

                @Override
                public int getSelectedIndex() {
                    return -1;
                }

                @Override
                public void clearSelection() {}

                @Override
                public void addChangeListener(ChangeListener listener) {}
            };
            pane.setModel(model);
            pane.add(new JLabel(TEXT));
            panel.add(pane);
            frame.add(panel);
            frame.setVisible(true);
        });

    }
}

Just launch this and the window displays, but also throws this exception several times:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 1
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.getTabBounds(BasicTabbedPaneUI.java:1738)
	at com.formdev.flatlaf.ui.FlatTabbedPaneUI.getTabBounds(FlatTabbedPaneUI.java:1881)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.getTabBounds(BasicTabbedPaneUI.java:1673)
	at com.formdev.flatlaf.ui.FlatTabbedPaneUI.paintContentBorder(FlatTabbedPaneUI.java:1690)
	at com.formdev.flatlaf.ui.FlatTabbedPaneUI.paint(FlatTabbedPaneUI.java:1170)
	at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
	at com.formdev.flatlaf.ui.FlatTabbedPaneUI.update(FlatTabbedPaneUI.java:1154)
	at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:842)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1119)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
	at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5318)
	at java.desktop/javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:246)
	at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1337)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1105)
	at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
	at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
	at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
	at java.desktop/java.awt.Container.paint(Container.java:2005)
	at java.desktop/java.awt.Window.paint(Window.java:3959)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:890)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:862)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:862)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:835)
	at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:784)
	at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1898)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

In FlatTabbedPaneUI.paint:

	int tabPlacement = tabPane.getTabPlacement();
	int selectedIndex = tabPane.getSelectedIndex();

	paintContentBorder( g, tabPlacement, selectedIndex );

Where getSelectedIndex() returns -1 this leads to the index exception, but from the SingleSelectionModel Javadoc:

  • @return the model's selection, or -1 if there is no selection

So this is a valid value for this method to return, but it is not properly handled by FlatTabbedPaneUI or BasicTabbedPaneUI.

@remcopoelstra
Copy link

I am curious about your use case, are you trying to create a TabbedPane that supports 'clearing' of the selection? (no tabs selected and empty content).

According to the documentation there will always be a selected tab:
https://docs.oracle.com/javase/8/docs/api/javax/swing/JTabbedPane.html

If the tab count is greater than 0, then there will always be a selected index, which by default will be initialized to the first tab.

@IanKrL
Copy link
Author

IanKrL commented Aug 2, 2024

You may be right, I didn't catch that line. I'll double check my real-life case and get back to you.

DevCharly added a commit that referenced this issue Aug 2, 2024
…card" tab type and using a custom tab selection model that returns -1 for selected tab (issue #875)
@DevCharly
Copy link
Collaborator

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

@DevCharly DevCharly added this to the 3.5.1 milestone Aug 2, 2024
@IanKrL
Copy link
Author

IanKrL commented Aug 7, 2024

I looked more closely at my real-life case and what we were doing was actually more like this:

package stuffs;

import java.net.UnknownHostException;

import javax.swing.DefaultSingleSelectionModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SingleSelectionModel;
import javax.swing.SwingUtilities;

import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLightLaf;

public class SimpleLaf {
    private static final String TEXT = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG";

    public static void main(String[] args) throws UnknownHostException {
        SwingUtilities.invokeLater(() -> {
            JFrame.setDefaultLookAndFeelDecorated(true);
            FlatLightLaf.setup();
            final JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setTitle("Some Title");
            frame.setSize(350, 100);
            final JPanel panel = new JPanel();
            JTabbedPane pane = new JTabbedPane();
            pane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_TYPE,
                FlatClientProperties.TABBED_PANE_TAB_TYPE_CARD);
            SingleSelectionModel model = new DefaultSingleSelectionModel();
            pane.setModel(model);
            pane.add(new JLabel(TEXT));
            panel.add(pane);
            frame.add(panel);
            // ***** This call here *****
            pane.setSelectedIndex(-1);
            frame.setVisible(true);
        });

    }
}

That is, manually setting the selected index to -1. This value is set during a tab-collapse animation to indicate a changing state. Ideally I imagine I'd rewrite to not set the unexpected -1 while a tab is present, but it's pretty thorny to untangle the logic and probably not worth it at this point.

I did come up with a workaround to make flatlaf 3.3 happy (sorry I forgot to report the version in my original description) until I can upgrade to 3.5.1+. I overrode getSelectedIndex() in my subclass of JTabbedPane and just don't allow it to return a negative number when there are tabs. That allows my weird animation code to function, but looks kosher to the outside world.

Thanks for fixing, DevCharly.

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

3 participants