Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6368047

jtabbedpane does not fire state changed when inner tabs are removed

XMLWordPrintable

    • 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

            shickeysunw Shannon Hickey (Inactive)
            ndcosta Nelson Dcosta (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: