TabbedPane: support minimum and maximum tab widths

This commit is contained in:
Karl Tauber
2020-10-27 19:08:23 +01:00
parent c3781dc4b5
commit 71b1e07ba6
5 changed files with 167 additions and 21 deletions

View File

@@ -19,6 +19,8 @@ FlatLaf Change Log
`JTabbedPane.trailingComponent` to a `java.awt.Component`) (PR #192; issue
#40)
- TabbedPane: Support closable tabs. (PR #193; issues #31 and #40)
- TabbedPane: Support minimum or maximum tab widths. (set client property
`JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer)
- Support painting separator line between window title and content (use UI value
`TitlePane.borderColor`). (issue #184)
- Extras: `FlatSVGIcon` now allows specifying icon width and height in

View File

@@ -246,6 +246,27 @@ public interface FlatClientProperties
*/
String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder";
/**
* Specifies the minimum width of a tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TABBED_PANE_MINIMUM_TAB_WIDTH = "JTabbedPane.minimumTabWidth";
/**
* Specifies the maximum width of a tab.
* <p>
* Applied only if tab does not have a custom tab component
* (see {@link javax.swing.JTabbedPane#setTabComponentAt(int, java.awt.Component)}).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TABBED_PANE_MAXIMUM_TAB_WIDTH = "JTabbedPane.maximumTabWidth";
/**
* Specifies the height of a tab.
* <p>

View File

@@ -118,6 +118,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.focusColor Color
* @uiDefault TabbedPane.tabSeparatorColor Color optional; defaults to TabbedPane.contentAreaColor
* @uiDefault TabbedPane.contentAreaColor Color
* @uiDefault TabbedPane.minimumTabWidth int optional
* @uiDefault TabbedPane.maximumTabWidth int optional
* @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int
@@ -153,12 +155,15 @@ public class FlatTabbedPaneUI
protected Color contentAreaColor;
private int textIconGapUnscaled;
protected int minimumTabWidth;
protected int maximumTabWidth;
protected int tabHeight;
protected int tabSelectionHeight;
protected int contentSeparatorHeight;
protected boolean showTabSeparators;
protected boolean tabSeparatorsFullHeight;
protected boolean hasFullBorder;
protected boolean tabsOpaque = true;
private String hiddenTabsNavigationStr;
protected Icon closeIcon;
@@ -217,12 +222,15 @@ public class FlatTabbedPaneUI
contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" );
textIconGapUnscaled = UIManager.getInt( "TabbedPane.textIconGap" );
minimumTabWidth = UIManager.getInt( "TabbedPane.minimumTabWidth" );
maximumTabWidth = UIManager.getInt( "TabbedPane.maximumTabWidth" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
@@ -518,8 +526,19 @@ public class FlatTabbedPaneUI
textIconGap = scale( textIconGapUnscaled );
int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
// make tab wider if closable
if( isTabClosable( tabIndex ) )
tabWidth += closeIcon.getIconWidth();
// apply minimum and maximum tab width
int min = getTabClientPropertyInt( tabIndex, TABBED_PANE_MINIMUM_TAB_WIDTH, minimumTabWidth );
int max = getTabClientPropertyInt( tabIndex, TABBED_PANE_MAXIMUM_TAB_WIDTH, maximumTabWidth );
if( min > 0 )
tabWidth = Math.max( tabWidth, scale( min ) );
if( max > 0 && tabPane.getTabComponentAt( tabIndex ) == null )
tabWidth = Math.min( tabWidth, scale( max ) );
return tabWidth;
}
@@ -649,6 +668,46 @@ public class FlatTabbedPaneUI
paintTabArea( g, tabPlacement, selectedIndex );
}
@Override
protected void paintTab( Graphics g, int tabPlacement, Rectangle[] rects,
int tabIndex, Rectangle iconRect, Rectangle textRect )
{
Rectangle tabRect = rects[tabIndex];
boolean isSelected = (tabIndex == tabPane.getSelectedIndex());
// paint background
if( tabsOpaque || tabPane.isOpaque() )
paintTabBackground( g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected );
// paint border
paintTabBorder( g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected );
if( tabPane.getTabComponentAt( tabIndex ) != null )
return;
// layout title and icon
String title = tabPane.getTitleAt( tabIndex );
Icon icon = getIconForTab( tabIndex );
Font font = tabPane.getFont();
FontMetrics metrics = tabPane.getFontMetrics( font );
String clippedTitle = layoutAndClipLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
// special title clipping for scroll layout where title off last visible tab on right side may be truncated
if( tabViewport != null && (tabPlacement == TOP || tabPlacement == BOTTOM) ) {
Rectangle viewRect = tabViewport.getViewRect();
viewRect.width -= 4; // subtract width of cropped edge
if( !viewRect.contains( textRect ) ) {
Rectangle r = viewRect.intersection( textRect );
if( r.x > viewRect.x )
clippedTitle = JavaCompatibility.getClippedString( null, metrics, title, r.width );
}
}
// paint title and icon
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected );
paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected );
}
@Override
protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics,
int tabIndex, String title, Rectangle textRect, boolean isSelected )
@@ -662,20 +721,6 @@ public class FlatTabbedPaneUI
return;
}
// clip title if our layout manager is used
// (normally this is done by invoker, but fails in this case)
if( tabViewport != null &&
(tabPlacement == TOP || tabPlacement == BOTTOM) )
{
Rectangle viewRect = tabViewport.getViewRect();
viewRect.width -= 4; // subtract width of cropped edge
if( !viewRect.contains( textRect ) ) {
Rectangle r = viewRect.intersection( textRect );
if( r.x > viewRect.x )
title = JavaCompatibility.getClippedString( null, metrics, title, r.width );
}
}
// plain text
Color color;
if( tabPane.isEnabled() && tabPane.isEnabledAt( tabIndex ) ) {
@@ -898,14 +943,37 @@ public class FlatTabbedPaneUI
{
}
@Override
protected void layoutLabel( int tabPlacement, FontMetrics metrics, int tabIndex, String title, Icon icon,
Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected )
protected String layoutAndClipLabel( int tabPlacement, FontMetrics metrics, int tabIndex,
String title, Icon icon, Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected )
{
// update textIconGap before used in super class
textIconGap = scale( textIconGapUnscaled );
// remove tab insets and space for close button from the tab rectangle
// to get correctly clipped title
tabRect = FlatUIUtils.subtractInsets( tabRect, getTabInsets( tabPlacement, tabIndex ) );
if( isTabClosable( tabIndex ) ) {
tabRect.width -= closeIcon.getIconWidth();
if( !isLeftToRight() )
tabRect.x += closeIcon.getIconWidth();
}
super.layoutLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
// reset rectangles
textRect.setBounds( 0, 0, 0, 0 );
iconRect.setBounds( 0, 0, 0, 0 );
// temporary set "html" client property on tabbed pane, which is used by SwingUtilities.layoutCompoundLabel()
View view = getTextViewForTab( tabIndex );
if( view != null )
tabPane.putClientProperty( "html", view );
// layout label
String clippedTitle = SwingUtilities.layoutCompoundLabel( tabPane, metrics, title, icon,
SwingUtilities.CENTER, SwingUtilities.CENTER,
SwingUtilities.CENTER, SwingUtilities.TRAILING,
tabRect, iconRect, textRect, scale( textIconGapUnscaled ) );
// remove temporary client property
tabPane.putClientProperty( "html", null );
return clippedTitle;
}
@Override
@@ -993,6 +1061,11 @@ public class FlatTabbedPaneUI
return tabPane.getClientProperty( key );
}
protected int getTabClientPropertyInt( int tabIndex, String key, int defaultValue ) {
Object value = getTabClientProperty( tabIndex, key );
return (value instanceof Integer) ? (int) value : defaultValue;
}
protected void ensureCurrentLayout() {
// since super.ensureCurrentLayout() is private,
// use super.getTabRunCount() as workaround
@@ -1718,6 +1791,8 @@ public class FlatTabbedPaneUI
case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_MINIMUM_TAB_WIDTH:
case TABBED_PANE_MAXIMUM_TAB_WIDTH:
case TABBED_PANE_TAB_HEIGHT:
case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
@@ -1757,6 +1832,8 @@ public class FlatTabbedPaneUI
protected void contentPropertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case TABBED_PANE_MINIMUM_TAB_WIDTH:
case TABBED_PANE_MAXIMUM_TAB_WIDTH:
case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate();

View File

@@ -323,6 +323,16 @@ public class FlatContainerTest
}
}
private void minimumTabWidthChanged() {
Integer minimumTabWidth = minimumTabWidthCheckBox.isSelected() ? 100 : null;
putTabbedPanesClientProperty( TABBED_PANE_MINIMUM_TAB_WIDTH, minimumTabWidth );
}
private void maximumTabWidthChanged() {
Integer maximumTabWidth = maximumTabWidthCheckBox.isSelected() ? 60 : null;
putTabbedPanesClientProperty( TABBED_PANE_MAXIMUM_TAB_WIDTH, maximumTabWidth );
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JPanel panel9 = new JPanel();
@@ -365,6 +375,8 @@ public class FlatContainerTest
trailingComponentCheckBox = new JCheckBox();
showTabSeparatorsCheckBox = new JCheckBox();
secondTabWiderCheckBox = new JCheckBox();
minimumTabWidthCheckBox = new JCheckBox();
maximumTabWidthCheckBox = new JCheckBox();
CellConstraints cc = new CellConstraints();
//======== this ========
@@ -480,6 +492,8 @@ public class FlatContainerTest
"[]" +
"[]para" +
"[]" +
"[]para" +
"[]" +
"[]"));
//---- tabScrollCheckBox ----
@@ -605,6 +619,16 @@ public class FlatContainerTest
secondTabWiderCheckBox.setText("Second Tab insets wider (4,20,4,20)");
secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged());
tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 6");
//---- minimumTabWidthCheckBox ----
minimumTabWidthCheckBox.setText("Minimum tab width (100)");
minimumTabWidthCheckBox.addActionListener(e -> minimumTabWidthChanged());
tabbedPaneControlPanel.add(minimumTabWidthCheckBox, "cell 2 7");
//---- maximumTabWidthCheckBox ----
maximumTabWidthCheckBox.setText("Maximum tab width (60)");
maximumTabWidthCheckBox.addActionListener(e -> maximumTabWidthChanged());
tabbedPaneControlPanel.add(maximumTabWidthCheckBox, "cell 2 8");
}
panel9.add(tabbedPaneControlPanel, cc.xywh(1, 11, 3, 1));
}
@@ -637,6 +661,8 @@ public class FlatContainerTest
private JCheckBox trailingComponentCheckBox;
private JCheckBox showTabSeparatorsCheckBox;
private JCheckBox secondTabWiderCheckBox;
private JCheckBox minimumTabWidthCheckBox;
private JCheckBox maximumTabWidthCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables
//---- class Tab1Panel ----------------------------------------------------

View File

@@ -132,7 +132,7 @@ new FormModel {
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestFrame$NoRightToLeftPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[][fill][]"
"$rowConstraints": "[center][][]para[][]para[][]"
"$rowConstraints": "[center][][]para[][]para[][]para[][]"
} ) {
name: "tabbedPaneControlPanel"
"opaque": false
@@ -381,6 +381,26 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 6"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "minimumTabWidthCheckBox"
"text": "Minimum tab width (100)"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "minimumTabWidthChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 7"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "maximumTabWidthCheckBox"
"text": "Maximum tab width (60)"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "maximumTabWidthChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 8"
} )
}, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11
"gridWidth": 3