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 minimum or maximum tab widths. (set client property
`JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer)
(PR #199)
- TabbedPane: Support alignment of tab area. (set client property
`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
`TitlePane.borderColor`). (issue #184)
- 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";
/**
* 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.
* <p>

View File

@@ -128,6 +128,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault TabbedPane.tabAreaAlignment String leading (default), center, trailing or fill
* @uiDefault TabbedPane.tabWidthMode String preferred (default), equal or compact
* @uiDefault ScrollPane.smoothScrolling boolean
* @uiDefault TabbedPane.closeIcon Icon
*
@@ -148,6 +149,10 @@ public class FlatTabbedPaneUI
protected static final int ALIGN_CENTER = 2;
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> focusBackwardTraversalKeys;
@@ -174,6 +179,7 @@ public class FlatTabbedPaneUI
private String hiddenTabsNavigationStr;
private String tabAreaAlignmentStr;
private String tabWidthModeStr;
protected Icon closeIcon;
protected String moreTabsButtonToolTipText;
@@ -241,6 +247,7 @@ public class FlatTabbedPaneUI
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
tabAreaAlignmentStr = UIManager.getString( "TabbedPane.tabAreaAlignment" );
tabWidthModeStr = UIManager.getString( "TabbedPane.tabWidthMode" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
Locale l = tabPane.getLocale();
@@ -529,12 +536,35 @@ public class FlatTabbedPaneUI
tabPane.repaint( r );
}
private boolean inCalculateEqual;
@Override
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
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
if( isTabClosable( tabIndex ) )
@@ -663,14 +693,26 @@ public class FlatTabbedPaneUI
int tabIndex, Rectangle iconRect, Rectangle textRect )
{
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());
// paint background
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
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 )
return;
@@ -680,9 +722,12 @@ public class FlatTabbedPaneUI
Icon icon = getIconForTab( tabIndex );
Font font = tabPane.getFont();
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 );
// 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) ) {
Rectangle viewRect = tabViewport.getViewRect();
viewRect.width -= 4; // subtract width of cropped edge
@@ -694,7 +739,8 @@ public class FlatTabbedPaneUI
}
// 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 );
}
@@ -747,17 +793,10 @@ public class FlatTabbedPaneUI
protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex,
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
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
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 ) {
@@ -1104,6 +1143,13 @@ public class FlatTabbedPaneUI
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 ) {
if( str == null )
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 ) {
LayoutManager layout = tabPane.getLayout();
if( layout instanceof FlatTabbedPaneScrollLayout ) {
@@ -1881,6 +1939,7 @@ public class FlatTabbedPaneUI
case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
case TABBED_PANE_TAB_AREA_ALIGNMENT:
case TABBED_PANE_TAB_WIDTH_MODE:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate();
tabPane.repaint();

View File

@@ -172,10 +172,8 @@ public class FlatContainerTest
? new ScaledImageIcon( new ImageIcon( getClass().getResource( "/com/formdev/flatlaf/testing/test" + iconSize + ".png" ) ) )
: null;
int tabCount = tabbedPane.getTabCount();
if( tabCount > 0 )
tabbedPane.setIconAt( 0, icon );
if( tabCount > 1 )
tabbedPane.setIconAt( 1, icon );
for( int i = 0; i < tabCount; i++ )
tabbedPane.setIconAt( i, icon );
}
private void customBorderChanged() {
@@ -252,6 +250,13 @@ public class FlatContainerTest
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() {
tabBackForegroundChanged( tabbedPane1 );
tabBackForegroundChanged( tabbedPane2 );
@@ -387,6 +392,8 @@ public class FlatContainerTest
tabIconSizeSpinner = new JSpinner();
JLabel tabAreaAlignmentLabel = new JLabel();
tabAreaAlignmentField = new JComboBox<>();
JLabel tabWidthModeLabel = new JLabel();
tabWidthModeField = new JComboBox<>();
tabsClosableCheckBox = new JCheckBox();
customBorderCheckBox = new JCheckBox();
tabAreaInsetsCheckBox = new JCheckBox();
@@ -600,6 +607,20 @@ public class FlatContainerTest
tabAreaAlignmentField.addActionListener(e -> tabAreaAlignmentChanged());
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.setText("Tabs closable");
tabsClosableCheckBox.addActionListener(e -> tabsClosableChanged());
@@ -690,6 +711,7 @@ public class FlatContainerTest
private JCheckBox tabIconsCheckBox;
private JSpinner tabIconSizeSpinner;
private JComboBox<String> tabAreaAlignmentField;
private JComboBox<String> tabWidthModeField;
private JCheckBox tabsClosableCheckBox;
private JCheckBox customBorderCheckBox;
private JCheckBox tabAreaInsetsCheckBox;

View File

@@ -285,6 +285,29 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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" ) {
name: "tabsClosableCheckBox"
"text": "Tabs closable"