JDK-6180941 : REGRESSION: JTabbedPane w/SCROLL_TAB_LAYOUT & mouselistener can't be tabbed
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2004-10-19
  • Updated: 2011-01-19
  • Resolved: 2006-03-09
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-b64)
Java HotSpot(TM) Client VM (build 1.5.0-b64, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows 2000 [Version 5.00.2195], but this bug exists in all Java environments

A DESCRIPTION OF THE PROBLEM :
If a mouseListener is added to the JTabbedPane subcomponents ScrollableTabPanel or ScrollableTabViewport, changing tab using the mouse is impossible when the tabbed pane uses  SCROLL_TAB_LAYOUT.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the program below (a.java) using JVM 1.4. Then use any java from 1.4 to run to show that it works. Then use any java from 1.5.0 (beta1,2,rc,or "golden") and you will be able to reproduce the problem.

In the code, you can uncomment 6 lines of code so that the subcomponents to the JTabbedPane ScrollableTabPanel or ScrollableTabViewport are not added for mouse listening. This will make it work in J2SE 5.0 but then no mouseListener is present for those components!

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The JTabbedPane when using SCROLL_TAB_LAYOUT should always accept mouseClick (when tab is enabled/visible) to change tab. This does not work with J2SE 5.0.
ACTUAL -
The JTabbedPane when using SCROLL_TAB_LAYOUT doesn't change tab at mouseClick (when tab is enabled/visible) using J2SE 5.0. This works fine under any Java 1.4.x.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;

public class a
{
  public static void main(String [] args)
    {
    JTabbedPane tp=new JTabbedPane();

    // The line of code below makes mouse action in JTabbedPane not
    // to work, i.e. doesn't change tab when clicked upon...
    tp.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);

    tp.addTab("First",new JPanel());
    tp.addTab("Second",new JPanel());
    tp.addTab("Third",new JPanel());
    tp.addTab("Fourth",new JPanel());
    tp.addTab("Fifth",new JPanel());
    tp.addTab("Sixth",new JPanel());
    tp.addTab("Seventh",new JPanel());

    JFrame f=new JFrame("Scroll tab layout in tabbed pane doesn't work");
    Container c=f.getContentPane();
    c.addContainerListener(new CL());
    c.setLayout(new BorderLayout());
    c.add(tp,BorderLayout.CENTER);
    f.setBounds(0,0,500,300);
    f.setVisible(true);
    }
}

class CL implements ContainerListener
{
  // Dummy mouse listener...
  MouseListener ma=new MouseAdapter()
    {
    public void mousePressed(MouseEvent e)
      {
      if ( !e.isConsumed() && (e.isPopupTrigger()
        || (e.getModifiers()&(InputEvent.BUTTON2_MASK|InputEvent.BUTTON3_MASK))!=0) )
        {
        System.out.println("Mouse button 2 pressed...");
        e.consume();
        }
      }
    };

  public void componentAdded(ContainerEvent e)
    {
    addMouseListener(e.getChild());
    }

  public void componentRemoved(ContainerEvent e)
    {
    removeMouseListener(e.getChild());
    }

  void addMouseListener(Component c)
    {
    // The 6 lines of code below makes sure a mouse listener is not
    // added to the Viewport. Uncomment to verify the problem...
    // String name=c.getClass().getName();
    // System.out.println("...adding mouse listener to "+name);
    // if ( name.endsWith("TabbedPaneUI$ScrollableTabPanel")
    //   || name.endsWith("TabbedPaneUI$ScrollableTabViewport") )
    //   ; // Don't add mouse listener!
    // else
    c.addMouseListener(ma);

    if ( c instanceof Container )
      {
      Container cn=(Container)c;
      cn.addContainerListener(this);
      Component [] cs=cn.getComponents();
      for ( int ii=cs.length-1; ii>=0; --ii )
        addMouseListener(cs[ii]);
      }
    }

  void removeMouseListener(Component c)
    {
    c.removeMouseListener(ma);

    if ( c instanceof Container )
      {
      Container cn=(Container)c;
      cn.removeContainerListener(this);
      Component [] cs=cn.getComponents();
      for ( int ii=cs.length-1; ii>=0; --ii )
        removeMouseListener(cs[ii]);
      }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The reason for having a mouseListener for all components and possibly subcomponents is to be able to track a pop-up menu displayed with mouse button 2. It should always be possible to add a mouseListener to a component without disrupting its functionality, otherwise this component should override addMouseListener to do other things.

The workaround means checking the class name of the components that are to be added for mouse listening and checking if the name is Viewport or Scrollpane subcomponent of the (e.g.) javax.swing.plaf.basic.BasicTabbedPaneUI.

See the code (uncomment 6 lines) for an example.

Release Regression From : 1.4.2
The above release value was the last known release where this 
bug was known to work. Since then there has been a regression.
###@###.### 10/19/04 00:17 GMT

Comments
EVALUATION I evaluated this bug once again and decided to close it as "will not fix" Let me explain why: JTabbedPane is a complex component and consists of several internal containers which are supposed to be hidden from the programmers we don't provide and special API to get e.g. TabbedPaneUI$ScrollableTabPanel, so updating those componets through getComponents() method is not recommendable and might be risky because inner structure of JTabbedPane might change from one version of JRE to another Here we have the following situation: in 1.4 we use TabbedPaneUI$ScrollableTabPanel to catch mouse events and process them properly, in 1.5 we changed the logic and don't add any mouse listeners to it, when a component doesn't have any mouse listeners it becomes "transparent" for mouse events and pass them the lower component. We rely on that transparency and that's why adding an unexpected mouse listener brake the logic The best practice is never change or rely to the inner structure of a Component The possible work-around is to skip components which don't have mouse listeners if (c.getListeners(MouseListener.class).length != 0) { c.addMouseListener(ma); }
09-03-2006

WORK AROUND //dont add MouseListener for the component //which doesnt have them and so are "transparent" for MouseEvents if (c.getListeners(MouseListener.class).length != 0) { c.addMouseListener(ma); }
09-03-2006

SUGGESTED FIX JTabbed
09-03-2006

EVALUATION If ScrollableTabViewport or ScrollableTabPanel have any MouseListener attached it leads to incorrect work of JTabbedPane, cause they lose "transparency" for mouse events and components which are under them don't get mouse events any more We have to suppress adding MouseListeners to those classes ###@###.### 10/27/04 18:05 GMT ###@###.### 10/28/04 10:38 GMT
27-10-2004