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 `JTabbedPane.trailingComponent` to a `java.awt.Component`) (PR #192; issue
#40) #40)
- 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
`JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer)
- 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

@@ -246,6 +246,27 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder"; 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. * Specifies the height of a tab.
* <p> * <p>

View File

@@ -118,6 +118,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.focusColor Color * @uiDefault TabbedPane.focusColor Color
* @uiDefault TabbedPane.tabSeparatorColor Color optional; defaults to TabbedPane.contentAreaColor * @uiDefault TabbedPane.tabSeparatorColor Color optional; defaults to TabbedPane.contentAreaColor
* @uiDefault TabbedPane.contentAreaColor Color * @uiDefault TabbedPane.contentAreaColor Color
* @uiDefault TabbedPane.minimumTabWidth int optional
* @uiDefault TabbedPane.maximumTabWidth int optional
* @uiDefault TabbedPane.tabHeight int * @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int * @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int * @uiDefault TabbedPane.contentSeparatorHeight int
@@ -153,12 +155,15 @@ public class FlatTabbedPaneUI
protected Color contentAreaColor; protected Color contentAreaColor;
private int textIconGapUnscaled; private int textIconGapUnscaled;
protected int minimumTabWidth;
protected int maximumTabWidth;
protected int tabHeight; protected int tabHeight;
protected int tabSelectionHeight; protected int tabSelectionHeight;
protected int contentSeparatorHeight; protected int contentSeparatorHeight;
protected boolean showTabSeparators; protected boolean showTabSeparators;
protected boolean tabSeparatorsFullHeight; protected boolean tabSeparatorsFullHeight;
protected boolean hasFullBorder; protected boolean hasFullBorder;
protected boolean tabsOpaque = true;
private String hiddenTabsNavigationStr; private String hiddenTabsNavigationStr;
protected Icon closeIcon; protected Icon closeIcon;
@@ -217,12 +222,15 @@ public class FlatTabbedPaneUI
contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" ); contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" );
textIconGapUnscaled = UIManager.getInt( "TabbedPane.textIconGap" ); textIconGapUnscaled = UIManager.getInt( "TabbedPane.textIconGap" );
minimumTabWidth = UIManager.getInt( "TabbedPane.minimumTabWidth" );
maximumTabWidth = UIManager.getInt( "TabbedPane.maximumTabWidth" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" ); tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" ); tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" ); contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" ); showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" ); tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" ); hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" ); closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
@@ -518,8 +526,19 @@ public class FlatTabbedPaneUI
textIconGap = scale( textIconGapUnscaled ); textIconGap = scale( textIconGapUnscaled );
int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */; int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
// make tab wider if closable
if( isTabClosable( tabIndex ) ) if( isTabClosable( tabIndex ) )
tabWidth += closeIcon.getIconWidth(); 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; return tabWidth;
} }
@@ -649,6 +668,46 @@ public class FlatTabbedPaneUI
paintTabArea( g, tabPlacement, selectedIndex ); 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 @Override
protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics, protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics,
int tabIndex, String title, Rectangle textRect, boolean isSelected ) int tabIndex, String title, Rectangle textRect, boolean isSelected )
@@ -662,20 +721,6 @@ public class FlatTabbedPaneUI
return; 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 // plain text
Color color; Color color;
if( tabPane.isEnabled() && tabPane.isEnabledAt( tabIndex ) ) { if( tabPane.isEnabled() && tabPane.isEnabledAt( tabIndex ) ) {
@@ -898,14 +943,37 @@ public class FlatTabbedPaneUI
{ {
} }
@Override protected String layoutAndClipLabel( int tabPlacement, FontMetrics metrics, int tabIndex,
protected void layoutLabel( int tabPlacement, FontMetrics metrics, int tabIndex, String title, Icon icon, String title, Icon icon, Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected )
Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected )
{ {
// update textIconGap before used in super class // remove tab insets and space for close button from the tab rectangle
textIconGap = scale( textIconGapUnscaled ); // 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 @Override
@@ -993,6 +1061,11 @@ public class FlatTabbedPaneUI
return tabPane.getClientProperty( key ); 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() { protected void ensureCurrentLayout() {
// since super.ensureCurrentLayout() is private, // since super.ensureCurrentLayout() is private,
// use super.getTabRunCount() as workaround // use super.getTabRunCount() as workaround
@@ -1718,6 +1791,8 @@ public class FlatTabbedPaneUI
case TABBED_PANE_SHOW_TAB_SEPARATORS: case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER: 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_HEIGHT:
case TABBED_PANE_TAB_INSETS: case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION: case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
@@ -1757,6 +1832,8 @@ public class FlatTabbedPaneUI
protected void contentPropertyChange( PropertyChangeEvent e ) { protected void contentPropertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case TABBED_PANE_MINIMUM_TAB_WIDTH:
case TABBED_PANE_MAXIMUM_TAB_WIDTH:
case TABBED_PANE_TAB_INSETS: case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_TAB_CLOSABLE: case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate(); 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() { private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JPanel panel9 = new JPanel(); JPanel panel9 = new JPanel();
@@ -365,6 +375,8 @@ public class FlatContainerTest
trailingComponentCheckBox = new JCheckBox(); trailingComponentCheckBox = new JCheckBox();
showTabSeparatorsCheckBox = new JCheckBox(); showTabSeparatorsCheckBox = new JCheckBox();
secondTabWiderCheckBox = new JCheckBox(); secondTabWiderCheckBox = new JCheckBox();
minimumTabWidthCheckBox = new JCheckBox();
maximumTabWidthCheckBox = new JCheckBox();
CellConstraints cc = new CellConstraints(); CellConstraints cc = new CellConstraints();
//======== this ======== //======== this ========
@@ -480,6 +492,8 @@ public class FlatContainerTest
"[]" + "[]" +
"[]para" + "[]para" +
"[]" + "[]" +
"[]para" +
"[]" +
"[]")); "[]"));
//---- tabScrollCheckBox ---- //---- tabScrollCheckBox ----
@@ -605,6 +619,16 @@ public class FlatContainerTest
secondTabWiderCheckBox.setText("Second Tab insets wider (4,20,4,20)"); secondTabWiderCheckBox.setText("Second Tab insets wider (4,20,4,20)");
secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged()); secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged());
tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 6"); 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)); panel9.add(tabbedPaneControlPanel, cc.xywh(1, 11, 3, 1));
} }
@@ -637,6 +661,8 @@ public class FlatContainerTest
private JCheckBox trailingComponentCheckBox; private JCheckBox trailingComponentCheckBox;
private JCheckBox showTabSeparatorsCheckBox; private JCheckBox showTabSeparatorsCheckBox;
private JCheckBox secondTabWiderCheckBox; private JCheckBox secondTabWiderCheckBox;
private JCheckBox minimumTabWidthCheckBox;
private JCheckBox maximumTabWidthCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
//---- class Tab1Panel ---------------------------------------------------- //---- 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 ) { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestFrame$NoRightToLeftPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3" "$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[][fill][]" "$columnConstraints": "[][fill][]"
"$rowConstraints": "[center][][]para[][]para[][]" "$rowConstraints": "[center][][]para[][]para[][]para[][]"
} ) { } ) {
name: "tabbedPaneControlPanel" name: "tabbedPaneControlPanel"
"opaque": false "opaque": false
@@ -381,6 +381,26 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 6" "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 ) { }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11 "gridY": 11
"gridWidth": 3 "gridWidth": 3