JDK-8215396 : JTabbedPane preferred size calculation is wrong for SCROLL_TAB_LAYOUT
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 7,8,9,10,11,12
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2018-12-06
  • Updated: 2022-02-10
  • Resolved: 2019-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13
11.0.7Fixed 13 b14Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 10 Pro, 64 bit
Tested on:
JDK 1.8.0_192, 64 bit
JDK 1.9.0_01, 64 bit
JDK 1.10.0_02, 64 bit
JDK 1.11.0_01, 64 bit

A DESCRIPTION OF THE PROBLEM :
I would like to reopen "JDK-7151452 : JTabbedPane preferred size calculation
is wrong for SCROLL_TAB_LAYOUT"
(https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7151452).
I am still experiencing the same problem, so as suggested in a comment to the
original report, I am reopening it under a new version. It was suggested
to specify 9 as the affected version, but that's not possible anymore, so I
used 10, but I experience the same problem in versions 8, 9, and 11.

Also, I found where the problem is in the code and how to fix it.
Namely, the problem is in the following methods of the
BasicTabbedPaneUI.TabbedPaneScrollLayout inner class:

    protected int preferredTabAreaHeight(int tabPlacement, int width) {
        return calculateMaxTabHeight(tabPlacement);
    }

    protected int preferredTabAreaWidth(int tabPlacement, int height) {
        return calculateMaxTabWidth(tabPlacement);
    }

These methods are defined in the superclass, BasicTabbedPaneUI.TabbedPaneLayout,
and overridden here. While in the superclass these methods take into account the
tab area insents, the overridden methods don't, and so they break the implicit
contract and yield a number that is too small.

Therefore, the solution is to add the correct insets in the overridden methods
to the result before returning it. There are two ways of doing this. One is to
do it manually in these methods, like:

    protected int preferredTabAreaHeight(int tabPlacement, int width) {
        Insets tabAreaInsets = getTabAreaInsets(tabPlacements);
        return calculateMaxTabHeight(tabPlacement) + tabAreaInsets.top + tabAreaInsets.bottom;
    }

    protected int preferredTabAreaWidth(int tabPlacement, int height) {
        Insets tabAreaInsets = getTabAreaInsets(tabPlacements);
        return calculateMaxTabWidth(tabPlacement) + tabAreaInsets.left + tabAreaInsets.right;
    }

The other solution is to use calculateTabAreaHeight() and
calculateTabAreaWidth(), respectively:

    protected int preferredTabAreaHeight(int tabPlacement, int width) {
        return calculateTabAreaHeight(tabPlacement, 1, calculateMaxTabHeight(tabPlacement));
    }

    protected int preferredTabAreaWidth(int tabPlacement, int height) {
        return calculateTabAreaWidth(tabPlacement, 1, calculateMaxTabWidth(tabPlacement));
    }

I find the second solution cleaner since we are reusing the existing methods for
calculating the size instead of repeating the logic. These methods are also used
for calculating the size for the WRAP_TAB_LAYOUT, only that there the number of
rows/columns need to be calculated first, whereas here we can simply pass 1.

On the other hand, the first solution might have some slight performance benefit,
but maybe that's not significant.

Anyway, I provide both solutions so you can choose the one you prefer.

Finally, I wanted to add that while the original issue is only about preferred size, both the
problem and the fix apply to minimum size as well.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the original example (copied here for convenience), and compare the two frames.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Both frames should have the same size and show the full content.
ACTUAL -
Only the frame using WRAP_TAB_LAYOUT shows the full content, whereas the height of the one using SCROLL_TAB_LAYOUT is too small and thus does not show the full content.

---------- BEGIN SOURCE ----------
// This is copied from the original issue for convenience
package tabprob;

import java.awt.*;
import javax.swing.*;

public class TabProb extends JFrame {
  class FixLayout implements LayoutManager {
    @Override
    public void layoutContainer(Container C) {
      Insets in = C.getInsets();
      int w = 200 - in.left - in.right;
      int h = 100 - in.top - in.bottom;
      C.getComponents()[0].setBounds(in.top, in.left, w, h);
    }
    @Override
    public Dimension minimumLayoutSize(Container C) {
      return new Dimension(200, 100);
    }
    @Override
    public Dimension preferredLayoutSize(Container C) {
      return new Dimension(200, 100);
    }
    @Override
    public void removeLayoutComponent(Component c) {
    }
    @Override
    public void addLayoutComponent(String s, Component c) {
    }
  }

  public TabProb(int layoutPolicy) {
    JTabbedPane tabpanel = new JTabbedPane();
    tabpanel.setTabPlacement(JTabbedPane.TOP);
    tabpanel.setTabLayoutPolicy(layoutPolicy);
    JPanel panel = new JPanel(new FixLayout());
    JLabel label = new JLabel("TEST");
    label.setBorder(BorderFactory.createLineBorder(Color.green, 3));
    panel.add(label);
    tabpanel.add("TEST", panel);
    add(tabpanel, BorderLayout.CENTER);
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
  }

  public static void main(String[] args) {
    try {
      //UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
      //UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
      UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    } catch(Exception e) {
    }
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        TabProb tb1 = new TabProb(JTabbedPane.SCROLL_TAB_LAYOUT);
        tb1.pack();
        tb1.setVisible(true);
        
        TabProb tb2 = new TabProb(JTabbedPane.WRAP_TAB_LAYOUT);
        tb2.pack();
        tb2.setVisible(true);
      }
    });
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
From the original report:
1) Set a large enough inset for the contentpanel, or
2) Set TabbedPane.tabAreaInsets to Insets(0,0,0,0)

FREQUENCY : always



Comments
Fix Request (11u) Small low risk patch with its own test. Applies cleanly.
12-12-2019

URL: http://hg.openjdk.java.net/jdk/jdk/rev/7d5e595cb7aa User: psadhukhan Date: 2019-03-22 09:07:20 +0000
22-03-2019

URL: http://hg.openjdk.java.net/jdk/client/rev/7d5e595cb7aa User: psadhukhan Date: 2019-03-08 08:37:29 +0000
08-03-2019

There is another related issue, proposing the same fix: JDK-8046209
10-01-2019

As per description, JTabbedPane preferred size calculation is wrong for SCROLL_TAB_LAYOUT. There is an existing open report JDK-7151452, which was originally reported against 7u, but as per submitter the issue exists in JDK 8, 9, 10 and 11 as well. Checked this for the reported versions and could confirm the issue. Results: ======== 7: Fail 8u191: Fail 9: Fail 10.0.2: Fail 11: Fail 12 ea b23: Fail To verify, run the attached test case with respective versions. There are couple of suggestions w.r.t fix for this issue in the description. ==================== The problem is in the following methods of the BasicTabbedPaneUI.TabbedPaneScrollLayout inner class: protected int preferredTabAreaHeight(int tabPlacement, int width) { return calculateMaxTabHeight(tabPlacement); } protected int preferredTabAreaWidth(int tabPlacement, int height) { return calculateMaxTabWidth(tabPlacement); } These methods are defined in the superclass, BasicTabbedPaneUI.TabbedPaneLayout, and overridden here. While in the superclass these methods take into account the tab area insets, the overridden methods don't, and so they break the implicit contract and yield a number that is too small. Therefore, the solution is to add the correct insets in the overridden methods to the result before returning it. ========================
14-12-2018