-
Bug
-
Resolution: Fixed
-
P3
-
5.0, 6
-
b82
-
generic, x86
-
generic, windows_xp
FULL PRODUCT VERSION :
java version "1.5.0_05"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_05-b05)
Java HotSpot(TM) Client VM (build 1.5.0_05-b05, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Windows XP Version 5.1 (Build 2600.xpsp_sp2_gdr.050301-1519: Service Pack 2)
A DESCRIPTION OF THE PROBLEM :
when a tabbed pane is displayed and you delete tabs programatically ( say via a button ) , deleting outer tabs generate state changed but deleting inner tabs do not.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please find a Abbot base test fixture source code in this bug report.
You will need the Abbot jars in your class path.
http://abbot.sourceforge.net/doc/download.shtml
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
state changed must be fired when inner tabs are deleted
ACTUAL -
state changed is fired when outter tabs are deleted
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package com.tabbedpane.test;
//
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import junit.extensions.abbot.ComponentTestFixture;
import abbot.tester.ComponentTester;
import abbot.tester.JTabbedPaneLocation;
import abbot.tester.JTabbedPaneTester;
@SuppressWarnings("all")
public class JTabbedPaneCompTestCase extends ComponentTestFixture
{
private JTabbedPaneTester tester;
private JFrame f;
private JTabbedPane tab;
private JPanel firstPnl, secondPnl, thirdPnl, fourthPnl, controlPnl;
private JButton removeSecondBtn, removeFourthBtn;
private boolean fired;
//
protected void setUp()
{
try
{
EventQueue.invokeAndWait(new Runnable()
{
public void run()
{
firstPnl = new JPanel();
secondPnl = new JPanel();
thirdPnl = new JPanel();
fourthPnl = new JPanel();
controlPnl = new JPanel();
//
removeSecondBtn = new JButton("removeSecond");
removeSecondBtn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent pE)
{
tab.remove(secondPnl);
}
});
removeFourthBtn = new JButton("removeFourth");
removeFourthBtn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent pE)
{
tab.remove(fourthPnl);
}
});
controlPnl.add(removeSecondBtn);
controlPnl.add(removeFourthBtn);
//
tab = new JTabbedPane();
tab.add(firstPnl, "firstPnl");
tab.add(secondPnl, "secondPnl");
tab.add(thirdPnl, "thirdPnl");
tab.add(fourthPnl, "fourthPnl");
tab.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent pE)
{
fired = true;
}
});
//
f = new JFrame("JTabbedPaneTestCase");
f.add(tab, BorderLayout.CENTER);
f.add(controlPnl, BorderLayout.SOUTH);
f.setSize(new Dimension(300, 400));
f.setVisible(true);
centerOnScreen(f);
//
fired = false;
}
});
}
catch (InvocationTargetException ex)
{
ex.printStackTrace();
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
tester = (JTabbedPaneTester) ComponentTester.getTester(JTabbedPane.class);
}
//
public final void testRemoveFourthPnl()
{
tester.actionDelay(2000);
tester.actionSelectTab(tab, new JTabbedPaneLocation(3));
assertTrue("State Changed On Selection:", fired);
fired = false;
tester.actionDelay(2000);
tester.actionClick(removeFourthBtn);
tester.actionDelay(2000);
assertTrue("State Changed On Removal:", fired);
}
//
public final void testRemoveSecondPnl()
{
tester.actionDelay(2000);
tester.actionSelectTab(tab, new JTabbedPaneLocation(1));
assertTrue("State Changed On Selection:", fired);
fired = false;
tester.actionDelay(2000);
tester.actionClick(removeSecondBtn);
tester.actionDelay(2000);
assertTrue("State Changed On Removal:", fired);
}
//
@Override
protected void tearDown() throws Exception
{
f.setVisible(false);
f = null;
}
//
public JTabbedPaneCompTestCase(String name)
{
super(name);
}
//
public static void main(String[] args)
{
junit.extensions.abbot.TestHelper.runTests(args, JTabbedPaneCompTestCase.class);
}
//
private static final void centerOnScreen(Component pComponent)
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension componentSize = pComponent.getSize();
if (componentSize.height > screenSize.height)
{
componentSize.height = screenSize.height;
}
if (componentSize.width > screenSize.width)
{
componentSize.width = screenSize.width;
}
pComponent.setLocation((screenSize.width - componentSize.width) / 2,
(screenSize.height - componentSize.height) / 2);
}
}
---------- END SOURCE ----------
Contribution by java.net member leouser:
A DESCRIPTION OF THE FIX :
BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed
FILES AFFECTED: javax.swing.JTabbedPane.
JDK VERSION
jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
Discusion(embeded in test case):
/**
* BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed.
* Ive ran into this bug in real life and I don't like it! The problem is 2 fold
* 1rst in removeTabAt it only selects if were at the end index.
* 2nd in the DefaultSingleSelectionModel, we only fire a ChangeEvent if
* there is a change numerically. It doesn't matter if its different tab
* or not. To remedy this I have:
* made sure in removeTabAt always selects somethings. It also
* doesn't matter what remove is called, they all appear to route
* to removeTabAt which is where the selection happens.
* Subclassed DefaultSingleSelectionModel to fire in most cases a ChangeEvent
* if there was a selection, regardless of selected index. I chose subclassing
* over writing a new version because it saves alot of code duplication and
* there aren't too many gymnastics involved with making it fire, just a flag.
*
* Being able to know if a different Tab has been selected after removal is
* essential to the user. Ive had to play games with the JTabbedPane because
* of this behavior. This 'correct' behavior would eliminate alot of the
* shenanigans Ive had to write.
*
* TESTING STRATEGY:
* Hit the remove button and it will remove the current tab. Watch for output
* on the console. A different hashcode should come up as well as the correct
* tab name for the newly selected tab. To show the change execute this against
* an unmodified JTabbedPane and see the results.
*
* FILES AFFECTED: javax.swing.JTabbedPane.
* JDK VERSION
* jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
*
* test ran succesfully on a SUSE 7.3 Linux distribution
*
* Brian Harry
* ###@###.###
* Jan 20, 2006
*/
UNIFIED DIFF:
--- /home/nstuff/java6/jdk1.6.0/javax/swing/JTabbedPane.java Thu Dec 15 02:17:37 2005
+++ /home/javarefs/javax/swing/JTabbedPane.java Fri Jan 20 17:05:13 2006
@@ -176,7 +176,7 @@
setTabPlacement(tabPlacement);
setTabLayoutPolicy(tabLayoutPolicy);
pages = new Vector(1);
- setModel(new DefaultSingleSelectionModel());
+ setModel(new AlwaysFiringSingleSelectionModel());
updateUI();
}
@@ -859,6 +859,9 @@
}
}
+ if (selected < (tabCount - 1)) {
+ setSelectedIndexImpl(getSelectedIndex());
+ }
revalidate();
repaint();
}
@@ -2213,4 +2216,25 @@
}
return -1;
}
+
+ @SuppressWarnings("serial")
+ private static class AlwaysFiringSingleSelectionModel extends DefaultSingleSelectionModel{
+
+ boolean fired;
+ @Override
+ public void setSelectedIndex(int index){
+ fired = false;
+ super.setSelectedIndex(index);
+ if(!fired && index != -1)
+ fireStateChanged();
+ }
+
+ @Override
+ public void fireStateChanged(){
+ fired = true;
+ super.fireStateChanged();
+ }
+
+ }
+
}
JUnit TESTCASE :
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import static java.lang.System.out;
/**
* BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed.
* Ive ran into this bug in real life and I don't like it! The problem is 2 fold
* 1rst in removeTabAt it only selects if were at the end index.
* 2nd in the DefaultSingleSelectionModel, we only fire a ChangeEvent if
* there is a change numerically. It doesn't matter if its different tab
* or not. To remedy this I have:
* made sure in removeTabAt always selects somethings. It also
* doesn't matter what remove is called, they all appear to route
* to removeTabAt which is where the selection happens.
* Subclassed DefaultSingleSelectionModel to fire in most cases a ChangeEvent
* if there was a selection, regardless of selected index. I chose subclassing
* over writing a new version because it saves alot of code duplication and
* there aren't too many gymnastics involved with making it fire, just a flag.
*
* Being able to know if a different Tab has been selected after removal is
* essential to the user. Ive had to play games with the JTabbedPane because
* of this behavior. This 'correct' behavior would eliminate alot of the
* shenanigans Ive had to write.
*
* TESTING STRATEGY:
* Hit the remove button and it will remove the current tab. Watch for output
* on the console. A different hashcode should come up as well as the correct
* tab name for the newly selected tab. To show the change execute this against
* an unmodified JTabbedPane and see the results.
*
* FILES AFFECTED: javax.swing.JTabbedPane.
* JDK VERSION
* jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
*
* test ran succesfully on a SUSE 7.3 Linux distribution
*
* Brian Harry
* ###@###.###
* Jan 20, 2006
*/
public class TestTab implements ChangeListener{
JTabbedPane jtp;
public void stateChanged(ChangeEvent ce){
out.println(ce);
Component c = jtp.getSelectedComponent();
int i = jtp.getSelectedIndex();
if(c != null){
out.println(c.hashCode());
out.println("Selected Name should be: " + jtp.getTitleAt(i));
}
}
public void testTabbedPane(){
JFrame jf = new JFrame();
final JTabbedPane jtp = this.jtp = new JTabbedPane();
jf.add(jtp);
for(int i = 0; i < 10; i++)
jtp.addTab(String.valueOf(i), new JLabel(String.valueOf(i)));
jtp.addChangeListener(this);
Action remove = new AbstractAction("Remove Tab"){
public void actionPerformed(ActionEvent ae){
out.println("WATCH FOR A CHANGE EVENT!:");
//Component c = jtp.getSelectedComponent();
//jtp.remove(c);
int index = jtp.getSelectedIndex();
if(index >= 0)
jtp.remove(index);
//jtp.removeAll();
out.println("WAS THERE A CHANGE EVENT?");
}
};
JButton jb = new JButton(remove);
jf.add(jb, BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
}
public static void main(String ... args){
Runnable run = new Runnable(){
public void run(){
new TestTab().testTabbedPane();
}
};
SwingUtilities.invokeLater(run);
}
}
FIX FOR BUG NUMBER:
6368047
java version "1.5.0_05"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_05-b05)
Java HotSpot(TM) Client VM (build 1.5.0_05-b05, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Windows XP Version 5.1 (Build 2600.xpsp_sp2_gdr.050301-1519: Service Pack 2)
A DESCRIPTION OF THE PROBLEM :
when a tabbed pane is displayed and you delete tabs programatically ( say via a button ) , deleting outer tabs generate state changed but deleting inner tabs do not.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please find a Abbot base test fixture source code in this bug report.
You will need the Abbot jars in your class path.
http://abbot.sourceforge.net/doc/download.shtml
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
state changed must be fired when inner tabs are deleted
ACTUAL -
state changed is fired when outter tabs are deleted
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package com.tabbedpane.test;
//
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import junit.extensions.abbot.ComponentTestFixture;
import abbot.tester.ComponentTester;
import abbot.tester.JTabbedPaneLocation;
import abbot.tester.JTabbedPaneTester;
@SuppressWarnings("all")
public class JTabbedPaneCompTestCase extends ComponentTestFixture
{
private JTabbedPaneTester tester;
private JFrame f;
private JTabbedPane tab;
private JPanel firstPnl, secondPnl, thirdPnl, fourthPnl, controlPnl;
private JButton removeSecondBtn, removeFourthBtn;
private boolean fired;
//
protected void setUp()
{
try
{
EventQueue.invokeAndWait(new Runnable()
{
public void run()
{
firstPnl = new JPanel();
secondPnl = new JPanel();
thirdPnl = new JPanel();
fourthPnl = new JPanel();
controlPnl = new JPanel();
//
removeSecondBtn = new JButton("removeSecond");
removeSecondBtn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent pE)
{
tab.remove(secondPnl);
}
});
removeFourthBtn = new JButton("removeFourth");
removeFourthBtn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent pE)
{
tab.remove(fourthPnl);
}
});
controlPnl.add(removeSecondBtn);
controlPnl.add(removeFourthBtn);
//
tab = new JTabbedPane();
tab.add(firstPnl, "firstPnl");
tab.add(secondPnl, "secondPnl");
tab.add(thirdPnl, "thirdPnl");
tab.add(fourthPnl, "fourthPnl");
tab.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent pE)
{
fired = true;
}
});
//
f = new JFrame("JTabbedPaneTestCase");
f.add(tab, BorderLayout.CENTER);
f.add(controlPnl, BorderLayout.SOUTH);
f.setSize(new Dimension(300, 400));
f.setVisible(true);
centerOnScreen(f);
//
fired = false;
}
});
}
catch (InvocationTargetException ex)
{
ex.printStackTrace();
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
tester = (JTabbedPaneTester) ComponentTester.getTester(JTabbedPane.class);
}
//
public final void testRemoveFourthPnl()
{
tester.actionDelay(2000);
tester.actionSelectTab(tab, new JTabbedPaneLocation(3));
assertTrue("State Changed On Selection:", fired);
fired = false;
tester.actionDelay(2000);
tester.actionClick(removeFourthBtn);
tester.actionDelay(2000);
assertTrue("State Changed On Removal:", fired);
}
//
public final void testRemoveSecondPnl()
{
tester.actionDelay(2000);
tester.actionSelectTab(tab, new JTabbedPaneLocation(1));
assertTrue("State Changed On Selection:", fired);
fired = false;
tester.actionDelay(2000);
tester.actionClick(removeSecondBtn);
tester.actionDelay(2000);
assertTrue("State Changed On Removal:", fired);
}
//
@Override
protected void tearDown() throws Exception
{
f.setVisible(false);
f = null;
}
//
public JTabbedPaneCompTestCase(String name)
{
super(name);
}
//
public static void main(String[] args)
{
junit.extensions.abbot.TestHelper.runTests(args, JTabbedPaneCompTestCase.class);
}
//
private static final void centerOnScreen(Component pComponent)
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension componentSize = pComponent.getSize();
if (componentSize.height > screenSize.height)
{
componentSize.height = screenSize.height;
}
if (componentSize.width > screenSize.width)
{
componentSize.width = screenSize.width;
}
pComponent.setLocation((screenSize.width - componentSize.width) / 2,
(screenSize.height - componentSize.height) / 2);
}
}
---------- END SOURCE ----------
Contribution by java.net member leouser:
A DESCRIPTION OF THE FIX :
BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed
FILES AFFECTED: javax.swing.JTabbedPane.
JDK VERSION
jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
Discusion(embeded in test case):
/**
* BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed.
* Ive ran into this bug in real life and I don't like it! The problem is 2 fold
* 1rst in removeTabAt it only selects if were at the end index.
* 2nd in the DefaultSingleSelectionModel, we only fire a ChangeEvent if
* there is a change numerically. It doesn't matter if its different tab
* or not. To remedy this I have:
* made sure in removeTabAt always selects somethings. It also
* doesn't matter what remove is called, they all appear to route
* to removeTabAt which is where the selection happens.
* Subclassed DefaultSingleSelectionModel to fire in most cases a ChangeEvent
* if there was a selection, regardless of selected index. I chose subclassing
* over writing a new version because it saves alot of code duplication and
* there aren't too many gymnastics involved with making it fire, just a flag.
*
* Being able to know if a different Tab has been selected after removal is
* essential to the user. Ive had to play games with the JTabbedPane because
* of this behavior. This 'correct' behavior would eliminate alot of the
* shenanigans Ive had to write.
*
* TESTING STRATEGY:
* Hit the remove button and it will remove the current tab. Watch for output
* on the console. A different hashcode should come up as well as the correct
* tab name for the newly selected tab. To show the change execute this against
* an unmodified JTabbedPane and see the results.
*
* FILES AFFECTED: javax.swing.JTabbedPane.
* JDK VERSION
* jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
*
* test ran succesfully on a SUSE 7.3 Linux distribution
*
* Brian Harry
* ###@###.###
* Jan 20, 2006
*/
UNIFIED DIFF:
--- /home/nstuff/java6/jdk1.6.0/javax/swing/JTabbedPane.java Thu Dec 15 02:17:37 2005
+++ /home/javarefs/javax/swing/JTabbedPane.java Fri Jan 20 17:05:13 2006
@@ -176,7 +176,7 @@
setTabPlacement(tabPlacement);
setTabLayoutPolicy(tabLayoutPolicy);
pages = new Vector(1);
- setModel(new DefaultSingleSelectionModel());
+ setModel(new AlwaysFiringSingleSelectionModel());
updateUI();
}
@@ -859,6 +859,9 @@
}
}
+ if (selected < (tabCount - 1)) {
+ setSelectedIndexImpl(getSelectedIndex());
+ }
revalidate();
repaint();
}
@@ -2213,4 +2216,25 @@
}
return -1;
}
+
+ @SuppressWarnings("serial")
+ private static class AlwaysFiringSingleSelectionModel extends DefaultSingleSelectionModel{
+
+ boolean fired;
+ @Override
+ public void setSelectedIndex(int index){
+ fired = false;
+ super.setSelectedIndex(index);
+ if(!fired && index != -1)
+ fireStateChanged();
+ }
+
+ @Override
+ public void fireStateChanged(){
+ fired = true;
+ super.fireStateChanged();
+ }
+
+ }
+
}
JUnit TESTCASE :
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import static java.lang.System.out;
/**
* BUGID: 6368047 jtabbedpane does not fire state changed when inner tabs are removed.
* Ive ran into this bug in real life and I don't like it! The problem is 2 fold
* 1rst in removeTabAt it only selects if were at the end index.
* 2nd in the DefaultSingleSelectionModel, we only fire a ChangeEvent if
* there is a change numerically. It doesn't matter if its different tab
* or not. To remedy this I have:
* made sure in removeTabAt always selects somethings. It also
* doesn't matter what remove is called, they all appear to route
* to removeTabAt which is where the selection happens.
* Subclassed DefaultSingleSelectionModel to fire in most cases a ChangeEvent
* if there was a selection, regardless of selected index. I chose subclassing
* over writing a new version because it saves alot of code duplication and
* there aren't too many gymnastics involved with making it fire, just a flag.
*
* Being able to know if a different Tab has been selected after removal is
* essential to the user. Ive had to play games with the JTabbedPane because
* of this behavior. This 'correct' behavior would eliminate alot of the
* shenanigans Ive had to write.
*
* TESTING STRATEGY:
* Hit the remove button and it will remove the current tab. Watch for output
* on the console. A different hashcode should come up as well as the correct
* tab name for the newly selected tab. To show the change execute this against
* an unmodified JTabbedPane and see the results.
*
* FILES AFFECTED: javax.swing.JTabbedPane.
* JDK VERSION
* jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
*
* test ran succesfully on a SUSE 7.3 Linux distribution
*
* Brian Harry
* ###@###.###
* Jan 20, 2006
*/
public class TestTab implements ChangeListener{
JTabbedPane jtp;
public void stateChanged(ChangeEvent ce){
out.println(ce);
Component c = jtp.getSelectedComponent();
int i = jtp.getSelectedIndex();
if(c != null){
out.println(c.hashCode());
out.println("Selected Name should be: " + jtp.getTitleAt(i));
}
}
public void testTabbedPane(){
JFrame jf = new JFrame();
final JTabbedPane jtp = this.jtp = new JTabbedPane();
jf.add(jtp);
for(int i = 0; i < 10; i++)
jtp.addTab(String.valueOf(i), new JLabel(String.valueOf(i)));
jtp.addChangeListener(this);
Action remove = new AbstractAction("Remove Tab"){
public void actionPerformed(ActionEvent ae){
out.println("WATCH FOR A CHANGE EVENT!:");
//Component c = jtp.getSelectedComponent();
//jtp.remove(c);
int index = jtp.getSelectedIndex();
if(index >= 0)
jtp.remove(index);
//jtp.removeAll();
out.println("WAS THERE A CHANGE EVENT?");
}
};
JButton jb = new JButton(remove);
jf.add(jb, BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
}
public static void main(String ... args){
Runnable run = new Runnable(){
public void run(){
new TestTab().testTabbedPane();
}
};
SwingUtilities.invokeLater(run);
}
}
FIX FOR BUG NUMBER:
6368047
- relates to
-
JDK-5089436 REGRESSION: requestFocusInWindow() fails for comp. on JTabbedPane aftertab switch
- Resolved
-
JDK-6420152 REGRESSION: Closing an output tab in NetBeans produces ArrayIndexOutOfBoundsException
- Resolved