TabbedPane: support equal and compact tab width modes

This commit is contained in:
Karl Tauber
2020-10-29 19:26:09 +01:00
parent 0374c65159
commit da9d7a0dee
5 changed files with 157 additions and 17 deletions

View File

@@ -21,9 +21,13 @@ FlatLaf Change Log
- TabbedPane: Support closable tabs. (PR #193; issues #31 and #40) - TabbedPane: Support closable tabs. (PR #193; issues #31 and #40)
- TabbedPane: Support minimum or maximum tab widths. (set client property - TabbedPane: Support minimum or maximum tab widths. (set client property
`JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer) `JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer)
(PR #199)
- TabbedPane: Support alignment of tab area. (set client property - TabbedPane: Support alignment of tab area. (set client property
`JTabbedPane.tabAreaAlignment` to `"leading"`, `"trailing"`, `"center"` or `JTabbedPane.tabAreaAlignment` to `"leading"`, `"trailing"`, `"center"` or
`"fill"`) `"fill"`) (PR #199)
- TabbedPane: Support equal and compact tab width modes. (set client property
`JTabbedPane.tabWidthMode` to `"preferred"`, `"equal"` or `"compact"`) (PR
#199)
- Support painting separator line between window title and content (use UI value - Support painting separator line between window title and content (use UI value
`TitlePane.borderColor`). (issue #184) `TitlePane.borderColor`). (issue #184)
- Extras: `FlatSVGIcon` now allows specifying icon width and height in - Extras: `FlatSVGIcon` now allows specifying icon width and height in

View File

@@ -410,6 +410,38 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_TAB_AREA_ALIGN_FILL = "fill"; String TABBED_PANE_TAB_AREA_ALIGN_FILL = "fill";
/**
* Specifies how the tabs should be sized.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.String}
* <strong>Allowed Values</strong> {@link #TABBED_PANE_TAB_WIDTH_MODE_PREFERRED} (default),
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
*/
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
/**
* Tab width is adjusted to tab icon and title.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_PREFERRED = "preferred";
/**
* All tabs in a tabbed pane has same width.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal";
/**
* Unselected tabs are smaller because they show only the tab icon, but no tab title.
* Selected tabs show both.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
/** /**
* Specifies a component that will be placed at the leading edge of the tabs area. * Specifies a component that will be placed at the leading edge of the tabs area.
* <p> * <p>

View File

@@ -128,6 +128,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.hasFullBorder boolean * @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons * @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault TabbedPane.tabAreaAlignment String leading (default), center, trailing or fill * @uiDefault TabbedPane.tabAreaAlignment String leading (default), center, trailing or fill
* @uiDefault TabbedPane.tabWidthMode String preferred (default), equal or compact
* @uiDefault ScrollPane.smoothScrolling boolean * @uiDefault ScrollPane.smoothScrolling boolean
* @uiDefault TabbedPane.closeIcon Icon * @uiDefault TabbedPane.closeIcon Icon
* *
@@ -148,6 +149,10 @@ public class FlatTabbedPaneUI
protected static final int ALIGN_CENTER = 2; protected static final int ALIGN_CENTER = 2;
protected static final int ALIGN_FILL = 3; protected static final int ALIGN_FILL = 3;
protected static final int WIDTH_MODE_PREFERRED = 0;
protected static final int WIDTH_MODE_EQUAL = 1;
protected static final int WIDTH_MODE_COMPACT = 2;
private static Set<KeyStroke> focusForwardTraversalKeys; private static Set<KeyStroke> focusForwardTraversalKeys;
private static Set<KeyStroke> focusBackwardTraversalKeys; private static Set<KeyStroke> focusBackwardTraversalKeys;
@@ -174,6 +179,7 @@ public class FlatTabbedPaneUI
private String hiddenTabsNavigationStr; private String hiddenTabsNavigationStr;
private String tabAreaAlignmentStr; private String tabAreaAlignmentStr;
private String tabWidthModeStr;
protected Icon closeIcon; protected Icon closeIcon;
protected String moreTabsButtonToolTipText; protected String moreTabsButtonToolTipText;
@@ -241,6 +247,7 @@ public class FlatTabbedPaneUI
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" ); tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" ); hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
tabAreaAlignmentStr = UIManager.getString( "TabbedPane.tabAreaAlignment" ); tabAreaAlignmentStr = UIManager.getString( "TabbedPane.tabAreaAlignment" );
tabWidthModeStr = UIManager.getString( "TabbedPane.tabWidthMode" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" ); closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
Locale l = tabPane.getLocale(); Locale l = tabPane.getLocale();
@@ -529,12 +536,35 @@ public class FlatTabbedPaneUI
tabPane.repaint( r ); tabPane.repaint( r );
} }
private boolean inCalculateEqual;
@Override @Override
protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) { protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) {
int tabWidthMode = getTabWidthMode();
if( tabWidthMode == WIDTH_MODE_EQUAL && isHorizontalTabPlacement() && !inCalculateEqual ) {
inCalculateEqual = true;
try {
return calculateMaxTabWidth( tabPlacement );
} finally {
inCalculateEqual = false;
}
}
// update textIconGap before used in super class // update textIconGap before used in super class
textIconGap = scale( textIconGapUnscaled ); textIconGap = scale( textIconGapUnscaled );
int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */; int tabWidth;
Icon icon;
if( tabWidthMode == WIDTH_MODE_COMPACT &&
tabIndex != tabPane.getSelectedIndex() &&
isHorizontalTabPlacement() &&
tabPane.getTabComponentAt( tabIndex ) == null &&
(icon = getIconForTab( tabIndex )) != null )
{
Insets tabInsets = getTabInsets( tabPlacement, tabIndex );
tabWidth = icon.getIconWidth() + tabInsets.left + tabInsets.right;
} else
tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
// make tab wider if closable // make tab wider if closable
if( isTabClosable( tabIndex ) ) if( isTabClosable( tabIndex ) )
@@ -663,14 +693,26 @@ public class FlatTabbedPaneUI
int tabIndex, Rectangle iconRect, Rectangle textRect ) int tabIndex, Rectangle iconRect, Rectangle textRect )
{ {
Rectangle tabRect = rects[tabIndex]; Rectangle tabRect = rects[tabIndex];
int x = tabRect.x;
int y = tabRect.y;
int w = tabRect.width;
int h = tabRect.height;
boolean isSelected = (tabIndex == tabPane.getSelectedIndex()); boolean isSelected = (tabIndex == tabPane.getSelectedIndex());
// paint background // paint background
if( tabsOpaque || tabPane.isOpaque() ) if( tabsOpaque || tabPane.isOpaque() )
paintTabBackground( g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected ); paintTabBackground( g, tabPlacement, tabIndex, x, y, w, h, isSelected );
// paint border // paint border
paintTabBorder( g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected ); paintTabBorder( g, tabPlacement, tabIndex, x, y, w, h, isSelected );
// paint tab close button
if( isTabClosable( tabIndex ) )
paintTabCloseButton( g, tabIndex, x, y, w, h );
// paint selection indicator
if( isSelected )
paintTabSelection( g, tabPlacement, x, y, w, h );
if( tabPane.getTabComponentAt( tabIndex ) != null ) if( tabPane.getTabComponentAt( tabIndex ) != null )
return; return;
@@ -680,9 +722,12 @@ public class FlatTabbedPaneUI
Icon icon = getIconForTab( tabIndex ); Icon icon = getIconForTab( tabIndex );
Font font = tabPane.getFont(); Font font = tabPane.getFont();
FontMetrics metrics = tabPane.getFontMetrics( font ); FontMetrics metrics = tabPane.getFontMetrics( font );
boolean isCompact = (icon != null && !isSelected && getTabWidthMode() == WIDTH_MODE_COMPACT && isHorizontalTabPlacement());
if( isCompact )
title = null;
String clippedTitle = layoutAndClipLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected ); 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 // special title clipping for scroll layout where title of last visible tab on right side may be truncated
if( tabViewport != null && (tabPlacement == TOP || tabPlacement == BOTTOM) ) { if( tabViewport != null && (tabPlacement == TOP || tabPlacement == BOTTOM) ) {
Rectangle viewRect = tabViewport.getViewRect(); Rectangle viewRect = tabViewport.getViewRect();
viewRect.width -= 4; // subtract width of cropped edge viewRect.width -= 4; // subtract width of cropped edge
@@ -694,7 +739,8 @@ public class FlatTabbedPaneUI
} }
// paint title and icon // paint title and icon
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected ); if( !isCompact )
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected );
paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected ); paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected );
} }
@@ -747,17 +793,10 @@ public class FlatTabbedPaneUI
protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex, protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex,
int x, int y, int w, int h, boolean isSelected ) int x, int y, int w, int h, boolean isSelected )
{ {
// paint tab close button
if( isTabClosable( tabIndex ) )
paintTabCloseButton( g, tabIndex, x, y, w, h );
// paint tab separators // paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) ) !isLastInRun( tabIndex ) )
paintTabSeparator( g, tabPlacement, x, y, w, h ); paintTabSeparator( g, tabPlacement, x, y, w, h );
if( isSelected )
paintTabSelection( g, tabPlacement, x, y, w, h );
} }
protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) { protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) {
@@ -1104,6 +1143,13 @@ public class FlatTabbedPaneUI
return parseTabAreaAlignment( str ); return parseTabAreaAlignment( str );
} }
protected int getTabWidthMode() {
String str = (String) tabPane.getClientProperty( TABBED_PANE_TAB_WIDTH_MODE );
if( str == null )
str = tabWidthModeStr;
return parseTabWidthMode( str );
}
protected static int parseHiddenTabsNavigation( String str ) { protected static int parseHiddenTabsNavigation( String str ) {
if( str == null ) if( str == null )
return MORE_TABS_BUTTON; return MORE_TABS_BUTTON;
@@ -1128,6 +1174,18 @@ public class FlatTabbedPaneUI
} }
} }
protected static int parseTabWidthMode( String str ) {
if( str == null )
return WIDTH_MODE_PREFERRED;
switch( str ) {
default:
case TABBED_PANE_TAB_WIDTH_MODE_PREFERRED: return WIDTH_MODE_PREFERRED;
case TABBED_PANE_TAB_WIDTH_MODE_EQUAL: return WIDTH_MODE_EQUAL;
case TABBED_PANE_TAB_WIDTH_MODE_COMPACT: return WIDTH_MODE_COMPACT;
}
}
private void runWithOriginalLayoutManager( Runnable runnable ) { private void runWithOriginalLayoutManager( Runnable runnable ) {
LayoutManager layout = tabPane.getLayout(); LayoutManager layout = tabPane.getLayout();
if( layout instanceof FlatTabbedPaneScrollLayout ) { if( layout instanceof FlatTabbedPaneScrollLayout ) {
@@ -1881,6 +1939,7 @@ public class FlatTabbedPaneUI
case TABBED_PANE_TAB_INSETS: case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION: case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
case TABBED_PANE_TAB_AREA_ALIGNMENT: case TABBED_PANE_TAB_AREA_ALIGNMENT:
case TABBED_PANE_TAB_WIDTH_MODE:
case TABBED_PANE_TAB_CLOSABLE: case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); tabPane.repaint();

View File

@@ -172,10 +172,8 @@ public class FlatContainerTest
? new ScaledImageIcon( new ImageIcon( getClass().getResource( "/com/formdev/flatlaf/testing/test" + iconSize + ".png" ) ) ) ? new ScaledImageIcon( new ImageIcon( getClass().getResource( "/com/formdev/flatlaf/testing/test" + iconSize + ".png" ) ) )
: null; : null;
int tabCount = tabbedPane.getTabCount(); int tabCount = tabbedPane.getTabCount();
if( tabCount > 0 ) for( int i = 0; i < tabCount; i++ )
tabbedPane.setIconAt( 0, icon ); tabbedPane.setIconAt( i, icon );
if( tabCount > 1 )
tabbedPane.setIconAt( 1, icon );
} }
private void customBorderChanged() { private void customBorderChanged() {
@@ -252,6 +250,13 @@ public class FlatContainerTest
putTabbedPanesClientProperty( TABBED_PANE_TAB_AREA_ALIGNMENT, value ); putTabbedPanesClientProperty( TABBED_PANE_TAB_AREA_ALIGNMENT, value );
} }
private void tabWidthModeChanged() {
String value = (String) tabWidthModeField.getSelectedItem();
if( "default".equals( value ) )
value = null;
putTabbedPanesClientProperty( TABBED_PANE_TAB_WIDTH_MODE, value );
}
private void tabBackForegroundChanged() { private void tabBackForegroundChanged() {
tabBackForegroundChanged( tabbedPane1 ); tabBackForegroundChanged( tabbedPane1 );
tabBackForegroundChanged( tabbedPane2 ); tabBackForegroundChanged( tabbedPane2 );
@@ -387,6 +392,8 @@ public class FlatContainerTest
tabIconSizeSpinner = new JSpinner(); tabIconSizeSpinner = new JSpinner();
JLabel tabAreaAlignmentLabel = new JLabel(); JLabel tabAreaAlignmentLabel = new JLabel();
tabAreaAlignmentField = new JComboBox<>(); tabAreaAlignmentField = new JComboBox<>();
JLabel tabWidthModeLabel = new JLabel();
tabWidthModeField = new JComboBox<>();
tabsClosableCheckBox = new JCheckBox(); tabsClosableCheckBox = new JCheckBox();
customBorderCheckBox = new JCheckBox(); customBorderCheckBox = new JCheckBox();
tabAreaInsetsCheckBox = new JCheckBox(); tabAreaInsetsCheckBox = new JCheckBox();
@@ -600,6 +607,20 @@ public class FlatContainerTest
tabAreaAlignmentField.addActionListener(e -> tabAreaAlignmentChanged()); tabAreaAlignmentField.addActionListener(e -> tabAreaAlignmentChanged());
tabbedPaneControlPanel.add(tabAreaAlignmentField, "cell 1 3"); tabbedPaneControlPanel.add(tabAreaAlignmentField, "cell 1 3");
//---- tabWidthModeLabel ----
tabWidthModeLabel.setText("Tab width mode:");
tabbedPaneControlPanel.add(tabWidthModeLabel, "cell 2 3");
//---- tabWidthModeField ----
tabWidthModeField.setModel(new DefaultComboBoxModel<>(new String[] {
"default",
"preferred",
"equal",
"compact"
}));
tabWidthModeField.addActionListener(e -> tabWidthModeChanged());
tabbedPaneControlPanel.add(tabWidthModeField, "cell 2 3");
//---- tabsClosableCheckBox ---- //---- tabsClosableCheckBox ----
tabsClosableCheckBox.setText("Tabs closable"); tabsClosableCheckBox.setText("Tabs closable");
tabsClosableCheckBox.addActionListener(e -> tabsClosableChanged()); tabsClosableCheckBox.addActionListener(e -> tabsClosableChanged());
@@ -690,6 +711,7 @@ public class FlatContainerTest
private JCheckBox tabIconsCheckBox; private JCheckBox tabIconsCheckBox;
private JSpinner tabIconSizeSpinner; private JSpinner tabIconSizeSpinner;
private JComboBox<String> tabAreaAlignmentField; private JComboBox<String> tabAreaAlignmentField;
private JComboBox<String> tabWidthModeField;
private JCheckBox tabsClosableCheckBox; private JCheckBox tabsClosableCheckBox;
private JCheckBox customBorderCheckBox; private JCheckBox customBorderCheckBox;
private JCheckBox tabAreaInsetsCheckBox; private JCheckBox tabAreaInsetsCheckBox;

View File

@@ -285,6 +285,29 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 3" "value": "cell 1 3"
} ) } )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tabWidthModeLabel"
"text": "Tab width mode:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "tabWidthModeField"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "default"
addElement( "default" )
addElement( "preferred" )
addElement( "equal" )
addElement( "compact" )
}
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.typeParameters": "String"
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabWidthModeChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "tabsClosableCheckBox" name: "tabsClosableCheckBox"
"text": "Tabs closable" "text": "Tabs closable"