diff --git a/CHANGELOG.md b/CHANGELOG.md index a8482898..2f9a9123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,19 @@ 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) + (PR #199) +- TabbedPane: Support alignment of tab area. (set client property + `JTabbedPane.tabAreaAlignment` to `"leading"`, `"trailing"`, `"center"` or + `"fill"`) (PR #199) +- TabbedPane: Support equal and compact tab width modes. (set client property + `JTabbedPane.tabWidthMode` to `"preferred"`, `"equal"` or `"compact"`) (PR + #199) +- TabbedPane: Support left, right, top and bottom tab icon placement. (set + client property `JTabbedPane.tabIconPlacement` to `SwingConstants.LEADING`, + `SwingConstants.TRAILING`, `SwingConstants.TOP` or `SwingConstants.BOTTOM`) + (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 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java index f604288a..2cbf1ab4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf; import java.awt.Color; import java.util.Objects; import javax.swing.JComponent; +import javax.swing.SwingConstants; /** * @author Karl Tauber @@ -101,7 +102,7 @@ public interface FlatClientProperties /** * Specifies whether the button preferred size will be made square (quadratically). *

- * Components {@link javax.swing.JButton} and {@link javax.swing.JToggleButton} + * Components {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
* Value type {@link java.lang.Boolean} */ String SQUARE_SIZE = "JButton.squareSize"; @@ -113,7 +114,7 @@ public interface FlatClientProperties *

* Component {@link javax.swing.JButton}, {@link javax.swing.JToggleButton}, * {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner} and {@link javax.swing.text.JTextComponent}
- * Value type {@link java.lang.Integer}
+ * Value type {@link java.lang.Integer} */ String MINIMUM_WIDTH = "JComponent.minimumWidth"; @@ -121,7 +122,7 @@ public interface FlatClientProperties * Specifies minimum height of a component. *

* Component {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
- * Value type {@link java.lang.Integer}
+ * Value type {@link java.lang.Integer} */ String MINIMUM_HEIGHT = "JComponent.minimumHeight"; @@ -157,7 +158,7 @@ public interface FlatClientProperties * Paint the component with round edges. *

* Components {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner}, - * {@link javax.swing.JTextField}, {@link javax.swing.JFormattedTextField} and {@link javax.swing.JPasswordField} + * {@link javax.swing.JTextField}, {@link javax.swing.JFormattedTextField} and {@link javax.swing.JPasswordField}
* Value type {@link java.lang.Boolean} */ String COMPONENT_ROUND_RECT = "JComponent.roundRect"; @@ -246,6 +247,27 @@ public interface FlatClientProperties */ String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder"; + /** + * Specifies the minimum width of a tab. + *

+ * Component {@link javax.swing.JTabbedPane} + * or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
+ * Value type {@link java.lang.Integer} + */ + String TABBED_PANE_MINIMUM_TAB_WIDTH = "JTabbedPane.minimumTabWidth"; + + /** + * Specifies the maximum width of a tab. + *

+ * Applied only if tab does not have a custom tab component + * (see {@link javax.swing.JTabbedPane#setTabComponentAt(int, java.awt.Component)}). + *

+ * Component {@link javax.swing.JTabbedPane} + * or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
+ * Value type {@link java.lang.Integer} + */ + String TABBED_PANE_MAXIMUM_TAB_WIDTH = "JTabbedPane.maximumTabWidth"; + /** * Specifies the height of a tab. *

@@ -263,6 +285,14 @@ public interface FlatClientProperties */ String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"; + /** + * Specifies the insets of the tab area. + *

+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.awt.Insets} + */ + String TABBED_PANE_TAB_AREA_INSETS = "JTabbedPane.tabAreaInsets"; + /** * Specifies whether tabs are closable. * If set to {@code true} on a tabbed pane component, all tabs in that tabbed pane are closable. @@ -330,7 +360,7 @@ public interface FlatClientProperties * Specifies how to navigate to hidden tabs. *

* Component {@link javax.swing.JTabbedPane}
- * Value type {@link java.lang.String} + * Value type {@link java.lang.String}
* Allowed Values {@link #TABBED_PANE_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON} * or {@link #TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS} */ @@ -350,6 +380,88 @@ public interface FlatClientProperties */ String TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS = "arrowButtons"; + /** + * Specifies the alignment of the tab area. + *

+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.String}
+ * Allowed Values {@link #TABBED_PANE_TAB_AREA_ALIGN_LEADING} (default), + * {@link #TABBED_PANE_TAB_AREA_ALIGN_TRAILING}, {@link #TABBED_PANE_TAB_AREA_ALIGN_CENTER} + * or {@link #TABBED_PANE_TAB_AREA_ALIGN_FILL} + */ + String TABBED_PANE_TAB_AREA_ALIGNMENT = "JTabbedPane.tabAreaAlignment"; + + /** + * Align the tab area to the leading edge. + * + * @see #TABBED_PANE_TAB_AREA_ALIGNMENT + */ + String TABBED_PANE_TAB_AREA_ALIGN_LEADING = "leading"; + + /** + * Align the tab area to the trailing edge. + * + * @see #TABBED_PANE_TAB_AREA_ALIGNMENT + */ + String TABBED_PANE_TAB_AREA_ALIGN_TRAILING = "trailing"; + + /** + * Align the tab area to center. + * + * @see #TABBED_PANE_TAB_AREA_ALIGNMENT + */ + String TABBED_PANE_TAB_AREA_ALIGN_CENTER = "center"; + + /** + * Stretch tabs to fill all available space. + * + * @see #TABBED_PANE_TAB_AREA_ALIGNMENT + */ + String TABBED_PANE_TAB_AREA_ALIGN_FILL = "fill"; + + /** + * Specifies how the tabs should be sized. + *

+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.String}
+ * Allowed Values {@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 the tab icon placement (relative to tab title). + *

+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.Integer}
+ * Allowed Values {@link SwingConstants#LEADING} (default), + * {@link SwingConstants#TRAILING}, {@link SwingConstants#TOP} + * or {@link SwingConstants#BOTTOM} + */ + String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"; + /** * Specifies a component that will be placed at the leading edge of the tabs area. *

diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index 20447dae..212fd9ec 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -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 @@ -125,6 +127,8 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TabbedPane.tabSeparatorsFullHeight boolean * @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 * @@ -139,6 +143,16 @@ public class FlatTabbedPaneUI protected static final int MORE_TABS_BUTTON = 0; protected static final int ARROW_BUTTONS = 1; + // tab area alignment + protected static final int ALIGN_LEADING = 0; + protected static final int ALIGN_TRAILING = 1; + 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 focusForwardTraversalKeys; private static Set focusBackwardTraversalKeys; @@ -153,14 +167,19 @@ 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; + private String tabAreaAlignmentStr; + private String tabWidthModeStr; protected Icon closeIcon; protected String moreTabsButtonToolTipText; @@ -217,13 +236,18 @@ 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" ); + tabAreaAlignmentStr = UIManager.getString( "TabbedPane.tabAreaAlignment" ); + tabWidthModeStr = UIManager.getString( "TabbedPane.tabWidthMode" ); closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" ); Locale l = tabPane.getLocale(); @@ -512,39 +536,97 @@ 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 { + int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING ); + if( (iconPlacement == TOP || iconPlacement == BOTTOM) && + tabPane.getTabComponentAt( tabIndex ) == null && + (icon = getIconForTab( tabIndex )) != null ) + { + // TOP and BOTTOM icon placement + tabWidth = icon.getIconWidth(); + + View view = getTextViewForTab( tabIndex ); + if( view != null ) + tabWidth = Math.max( tabWidth, (int) view.getPreferredSpan( View.X_AXIS ) ); + else { + String title = tabPane.getTitleAt( tabIndex ); + if( title != null ) + tabWidth = Math.max( tabWidth, metrics.stringWidth( title ) ); + } + + Insets tabInsets = getTabInsets( tabPlacement, tabIndex ); + tabWidth += tabInsets.left + tabInsets.right; + } else + 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; } @Override protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) { - int tabHeight = scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight ) ); - return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ ); - } + int tabHeight; - protected int calculateMaxExtrasWidth() { - int width = 0; - if( leadingComponent != null ) - width = Math.max( width, leadingComponent.getPreferredSize().width ); - if( trailingComponent != null ) - width = Math.max( width, trailingComponent.getPreferredSize().width ); - return width; - } + Icon icon; + int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING ); + if( (iconPlacement == TOP || iconPlacement == BOTTOM) && + tabPane.getTabComponentAt( tabIndex ) == null && + (icon = getIconForTab( tabIndex )) != null ) + { + // TOP and BOTTOM icon placement + tabHeight = icon.getIconHeight(); - protected int calculateMaxExtrasHeight() { - int height = 0; - if( leadingComponent != null ) - height = Math.max( height, leadingComponent.getPreferredSize().height ); - if( trailingComponent != null ) - height = Math.max( height, trailingComponent.getPreferredSize().height ); - return height; + View view = getTextViewForTab( tabIndex ); + if( view != null ) + tabHeight += (int) view.getPreferredSpan( View.Y_AXIS ) + scale( textIconGapUnscaled ); + else if( tabPane.getTitleAt( tabIndex ) != null ) + tabHeight += fontHeight + scale( textIconGapUnscaled ); + + Insets tabInsets = getTabInsets( tabPlacement, tabIndex ); + tabHeight += tabInsets.top + tabInsets.bottom; + } else + tabHeight = super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */; + + return Math.max( tabHeight, scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight ) ) ); } @Override @@ -560,11 +642,14 @@ public class FlatTabbedPaneUI return new Insets( 0, 0, 0, 0 ); } - @Override - protected Insets getTabAreaInsets( int tabPlacement ) { + protected Insets getRealTabAreaInsets( int tabPlacement ) { Insets currentTabAreaInsets = super.getTabAreaInsets( tabPlacement ); Insets insets = (Insets) currentTabAreaInsets.clone(); + Object value = tabPane.getClientProperty( TABBED_PANE_TAB_AREA_INSETS ); + if( value instanceof Insets ) + rotateInsets( (Insets) value, insets, tabPlacement ); + // This is a "trick" to get rid of the cropped edge: // super.getTabAreaInsets() returns private field BasicTabbedPaneUI.currentTabAreaInsets, // which is also used to translate the origin of the cropped edge in @@ -575,21 +660,21 @@ public class FlatTabbedPaneUI // scale insets (before adding leading/trailing component sizes) insets = scale( insets ); + return insets; + } + + @Override + protected Insets getTabAreaInsets( int tabPlacement ) { + Insets insets = getRealTabAreaInsets( tabPlacement ); + // increase insets for wrap layout if using leading/trailing components if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT ) { - if( leadingComponent != null ) { - Dimension leadingSize = leadingComponent.getPreferredSize(); - if( isHorizontalTabPlacement() ) - insets.left += leadingSize.width; - else - insets.top += leadingSize.height; - } - if( trailingComponent != null ) { - Dimension trailingSize = trailingComponent.getPreferredSize(); - if( isHorizontalTabPlacement() ) - insets.right += trailingSize.width; - else - insets.bottom += trailingSize.height; + if( isHorizontalTabPlacement() ) { + insets.left += getLeadingPreferredWidth(); + insets.right += getTrailingPreferredWidth(); + } else { + insets.top += getLeadingPreferredHeight(); + insets.bottom += getTrailingPreferredHeight(); } } @@ -649,6 +734,62 @@ 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]; + 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, x, y, w, h, isSelected ); + + // paint border + 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; + + // layout title and icon + String title = tabPane.getTitleAt( tabIndex ); + 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 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 + 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 + if( !isCompact ) + 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 +803,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 ) ) { @@ -712,17 +839,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 ) { @@ -755,42 +875,10 @@ public class FlatTabbedPaneUI } protected void paintTabSelection( Graphics g, int tabPlacement, int x, int y, int w, int h ) { - // increase clip bounds in scroll-tab-layout to paint over the separator line - Rectangle clipBounds = isScrollTabLayout() ? g.getClipBounds() : null; - if( clipBounds != null && - this.contentSeparatorHeight != 0 && - clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) - { - Rectangle newClipBounds = new Rectangle( clipBounds ); - int contentSeparatorHeight = scale( this.contentSeparatorHeight ); - switch( tabPlacement ) { - case TOP: - default: - newClipBounds.height += contentSeparatorHeight; - break; - - case BOTTOM: - newClipBounds.y -= contentSeparatorHeight; - newClipBounds.height += contentSeparatorHeight; - break; - - case LEFT: - newClipBounds.width += contentSeparatorHeight; - break; - - case RIGHT: - newClipBounds.x -= contentSeparatorHeight; - newClipBounds.width += contentSeparatorHeight; - break; - } - g.setClip( newClipBounds ); - } - g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor ); - Insets contentInsets = getContentBorderInsets( tabPlacement ); - // paint underline selection + Insets contentInsets = getContentBorderInsets( tabPlacement ); int tabSelectionHeight = scale( this.tabSelectionHeight ); switch( tabPlacement ) { case TOP: @@ -812,9 +900,6 @@ public class FlatTabbedPaneUI g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h ); break; } - - if( clipBounds != null ) - g.setClip( clipBounds ); } /** @@ -885,8 +970,15 @@ public class FlatTabbedPaneUI if( isScrollTabLayout() && selectedIndex >= 0 && tabViewport != null ) { Rectangle tabRect = getTabBounds( tabPane, selectedIndex ); + // clip to "scrolling sides" of viewport + // (left and right if horizontal, top and bottom if vertical) Shape oldClip = g.getClip(); - g.setClip( tabViewport.getBounds() ); + Rectangle vr = tabViewport.getBounds(); + if( isHorizontalTabPlacement() ) + g.clipRect( vr.x, 0, vr.width, tabPane.getHeight() ); + else + g.clipRect( 0, vr.y, tabPane.getWidth(), vr.height ); + paintTabSelection( g, tabPlacement, tabRect.x, tabRect.y, tabRect.width, tabRect.height ); g.setClip( oldClip ); } @@ -898,14 +990,46 @@ 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 ); + // icon placement + int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING ); + int verticalTextPosition = CENTER; + int horizontalTextPosition = TRAILING; + switch( iconPlacement ) { + case TRAILING: horizontalTextPosition = LEADING; break; + case TOP: verticalTextPosition = BOTTOM; horizontalTextPosition = CENTER; break; + case BOTTOM: verticalTextPosition = TOP; horizontalTextPosition = CENTER; break; + } + + // 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, + CENTER, CENTER, verticalTextPosition, horizontalTextPosition, + tabRect, iconRect, textRect, scale( textIconGapUnscaled ) ); + + // remove temporary client property + tabPane.putClientProperty( "html", null ); + + return clippedTitle; } @Override @@ -993,6 +1117,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 @@ -1028,10 +1157,24 @@ public class FlatTabbedPaneUI } protected int getHiddenTabsNavigation() { - String hiddenTabsNavigationStr = (String) tabPane.getClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION ); - if( hiddenTabsNavigationStr == null ) - hiddenTabsNavigationStr = this.hiddenTabsNavigationStr; - return parseHiddenTabsNavigation( hiddenTabsNavigationStr ); + String str = (String) tabPane.getClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION ); + if( str == null ) + str = hiddenTabsNavigationStr; + return parseHiddenTabsNavigation( str ); + } + + protected int getTabAreaAlignment() { + String str = (String) tabPane.getClientProperty( TABBED_PANE_TAB_AREA_ALIGNMENT ); + if( str == null ) + str = tabAreaAlignmentStr; + 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 ) { @@ -1040,8 +1183,33 @@ public class FlatTabbedPaneUI switch( str ) { default: - case "moreTabsButton": return MORE_TABS_BUTTON; - case "arrowButtons": return ARROW_BUTTONS; + case TABBED_PANE_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON: return MORE_TABS_BUTTON; + case TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS: return ARROW_BUTTONS; + } + } + + protected static int parseTabAreaAlignment( String str ) { + if( str == null ) + return ALIGN_LEADING; + + switch( str ) { + default: + case TABBED_PANE_TAB_AREA_ALIGN_LEADING: return ALIGN_LEADING; + case TABBED_PANE_TAB_AREA_ALIGN_TRAILING: return ALIGN_TRAILING; + case TABBED_PANE_TAB_AREA_ALIGN_CENTER: return ALIGN_CENTER; + case TABBED_PANE_TAB_AREA_ALIGN_FILL: return ALIGN_FILL; + } + } + + 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; } } @@ -1076,6 +1244,22 @@ public class FlatTabbedPaneUI ((JComponent)tabViewport.getView()).scrollRectToVisible( (Rectangle) rects[selectedIndex].clone() ); } + private int getLeadingPreferredWidth() { + return (leadingComponent != null) ? leadingComponent.getPreferredSize().width : 0; + } + + private int getLeadingPreferredHeight() { + return (leadingComponent != null) ? leadingComponent.getPreferredSize().height : 0; + } + + private int getTrailingPreferredWidth() { + return (trailingComponent != null) ? trailingComponent.getPreferredSize().width : 0; + } + + private int getTrailingPreferredHeight() { + return (trailingComponent != null) ? trailingComponent.getPreferredSize().height : 0; + } + private void shiftTabs( int sx, int sy ) { if( sx == 0 && sy == 0 ) return; @@ -1092,6 +1276,64 @@ public class FlatTabbedPaneUI } } + private void stretchTabsWidth( int sw, boolean leftToRight ) { + int rsw = sw / rects.length; + int x = rects[0].x - (leftToRight ? 0 : rsw); + for( int i = 0; i < rects.length; i++ ) { + // fix tab component location + Component c = tabPane.getTabComponentAt( i ); + if( c != null ) + c.setLocation( x + (c.getX() - rects[i].x) + (rsw / 2), c.getY() ); + + // fix x location and width in rects + rects[i].x = x; + rects[i].width += rsw; + + if( leftToRight ) + x += rects[i].width; + else if( i + 1 < rects.length ) + x = rects[i].x - rects[i+1].width - rsw; + } + + // fix width of last tab + int diff = sw - (rsw * rects.length); + rects[rects.length-1].width += diff; + if( !leftToRight ) + rects[rects.length-1].x -= diff; + } + + private void stretchTabsHeight( int sh ) { + int rsh = sh / rects.length; + int y = rects[0].y; + for( int i = 0; i < rects.length; i++ ) { + // fix tab component location + Component c = tabPane.getTabComponentAt( i ); + if( c != null ) + c.setLocation( c.getX(), y + (c.getY() - rects[i].y) + (rsh / 2) ); + + // fix y location and height in rects + rects[i].y = y; + rects[i].height += rsh; + + y += rects[i].height; + } + + // fix height of last tab + rects[rects.length-1].height += (sh - (rsh * rects.length)); + } + + private int rectsTotalWidth( boolean leftToRight ) { + int last = rects.length - 1; + return leftToRight + ? (rects[last].x + rects[last].width) - rects[0].x + : (rects[0].x + rects[0].width) - rects[last].x; + } + + private int rectsTotalHeight() { + int last = rects.length - 1; + return (rects[last].y + rects[last].height) - rects[0].y; + } + //---- class TabCloseButton ----------------------------------------------- private class TabCloseButton @@ -1718,9 +1960,15 @@ 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_TAB_AREA_INSETS: case TABBED_PANE_HIDDEN_TABS_NAVIGATION: + case TABBED_PANE_TAB_AREA_ALIGNMENT: + case TABBED_PANE_TAB_WIDTH_MODE: + case TABBED_PANE_TAB_ICON_PLACEMENT: case TABBED_PANE_TAB_CLOSABLE: tabPane.revalidate(); tabPane.repaint(); @@ -1757,6 +2005,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(); @@ -1806,19 +2056,22 @@ public class FlatTabbedPaneUI Rectangle bounds = tabPane.getBounds(); Insets insets = tabPane.getInsets(); int tabPlacement = tabPane.getTabPlacement(); - Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); + int tabAreaAlignment = getTabAreaAlignment(); + Insets tabAreaInsets = getRealTabAreaInsets( tabPlacement ); boolean leftToRight = isLeftToRight(); // layout leading and trailing components in tab area if( tabPlacement == TOP || tabPlacement == BOTTOM ) { // fix x-locations of tabs in right-to-left component orientation if( !leftToRight ) - shiftTabs( insets.left + tabAreaInsets.right, 0 ); + shiftTabs( insets.left + tabAreaInsets.right + getTrailingPreferredWidth(), 0 ); // tab area height (maxTabHeight is zero if tab count is zero) int tabAreaHeight = (maxTabHeight > 0) ? maxTabHeight - : Math.max( calculateMaxExtrasHeight(), scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, tabHeight ) ) ); + : Math.max( + Math.max( getLeadingPreferredHeight(), getTrailingPreferredHeight() ), + scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, tabHeight ) ) ); // tab area bounds int tx = insets.left; @@ -1828,38 +2081,56 @@ public class FlatTabbedPaneUI int tw = bounds.width - insets.left - insets.right; int th = tabAreaHeight; + int leadingWidth = getLeadingPreferredWidth(); + int trailingWidth = getTrailingPreferredWidth(); + + // apply tab area alignment + if( runCount == 1 && rects.length > 0 ) { + int availWidth = tw - leadingWidth - trailingWidth - tabAreaInsets.left - tabAreaInsets.right; + int totalTabWidth = rectsTotalWidth( leftToRight ); + int diff = availWidth - totalTabWidth; + + switch( tabAreaAlignment ) { + case ALIGN_LEADING: + trailingWidth += diff; + break; + + case ALIGN_TRAILING: + shiftTabs( leftToRight ? diff : -diff, 0 ); + leadingWidth += diff; + break; + + case ALIGN_CENTER: + shiftTabs( (leftToRight ? diff : -diff) / 2, 0 ); + leadingWidth += diff / 2; + trailingWidth += diff - (diff / 2); + break; + + case ALIGN_FILL: + stretchTabsWidth( diff, leftToRight ); + break; + } + } else if( rects.length == 0 ) + trailingWidth = tw - leadingWidth; + // layout left component Container leftComponent = leftToRight ? leadingComponent : trailingComponent; if( leftComponent != null ) { - // trailing component fills all available horizontal space - int leftWidth = leftToRight - ? leftComponent.getPreferredSize().width - : (rects.length > 0 - ? rects[rects.length-1].x - tx - - (tabAreaInsets.right - trailingComponent.getPreferredSize().width) - : tw - (leadingComponent != null ? leadingComponent.getPreferredSize().width : 0)); + int leftWidth = leftToRight ? leadingWidth : trailingWidth; leftComponent.setBounds( tx, ty, leftWidth, th ); - - // reduce tab area bounds - tx += leftWidth; - tw -= leftWidth; } // layout right component Container rightComponent = leftToRight ? trailingComponent : leadingComponent; if( rightComponent != null ) { - // trailing component fills all available horizontal space - int rightWidth = !leftToRight - ? rightComponent.getPreferredSize().width - : (rects.length > 0 - ? tx + tw - (rects[rects.length-1].x + rects[rects.length-1].width) - - (tabAreaInsets.right - trailingComponent.getPreferredSize().width) - : tw); + int rightWidth = leftToRight ? trailingWidth : leadingWidth; rightComponent.setBounds( tx + tw - rightWidth, ty, rightWidth, th ); } } else { // LEFT and RIGHT tab placement // tab area width (maxTabWidth is zero if tab count is zero) - int tabAreaWidth = (maxTabWidth > 0) ? maxTabWidth : calculateMaxExtrasWidth(); + int tabAreaWidth = (maxTabWidth > 0) + ? maxTabWidth + : Math.max( getLeadingPreferredWidth(), getTrailingPreferredWidth() ); // tab area bounds int tx = (tabPlacement == LEFT) @@ -1869,25 +2140,45 @@ public class FlatTabbedPaneUI int tw = tabAreaWidth; int th = bounds.height - insets.top - insets.bottom; + int topHeight = getLeadingPreferredHeight(); + int bottomHeight = getTrailingPreferredHeight(); + + // apply tab area alignment + if( runCount == 1 && rects.length > 0 ) { + int availHeight = th - topHeight - bottomHeight - tabAreaInsets.top - tabAreaInsets.bottom; + int totalTabHeight = rectsTotalHeight(); + int diff = availHeight - totalTabHeight; + + switch( tabAreaAlignment ) { + case ALIGN_LEADING: + bottomHeight += diff; + break; + + case ALIGN_TRAILING: + shiftTabs( 0, diff ); + topHeight += diff; + break; + + case ALIGN_CENTER: + shiftTabs( 0, (diff) / 2 ); + topHeight += diff / 2; + bottomHeight += diff - (diff / 2); + break; + + case ALIGN_FILL: + stretchTabsHeight( diff ); + break; + } + } else if( rects.length == 0 ) + bottomHeight = th - topHeight; + // layout top component - if( leadingComponent != null ) { - int topHeight = leadingComponent.getPreferredSize().height; + if( leadingComponent != null ) leadingComponent.setBounds( tx, ty, tw, topHeight ); - // reduce tab area bounds - ty += topHeight; - th -= topHeight; - } - // layout bottom component - if( trailingComponent != null ) { - // trailing component fills all available vertical space - int bottomHeight = (rects.length > 0) - ? ty + th - (rects[rects.length-1].y + rects[rects.length-1].height) - - (tabAreaInsets.bottom - trailingComponent.getPreferredSize().height) - : th; + if( trailingComponent != null ) trailingComponent.setBounds( tx, ty + th - bottomHeight, tw, bottomHeight ); - } } } } @@ -1968,7 +2259,8 @@ public class FlatTabbedPaneUI Rectangle bounds = tabPane.getBounds(); Insets insets = tabPane.getInsets(); int tabPlacement = tabPane.getTabPlacement(); - Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); + int tabAreaAlignment = getTabAreaAlignment(); + Insets tabAreaInsets = getRealTabAreaInsets( tabPlacement ); Dimension moreButtonSize = useMoreButton ? moreTabsButton.getPreferredSize() : null; Dimension backwardButtonSize = useMoreButton ? null : backwardButton.getPreferredSize(); Dimension forwardButtonSize = useMoreButton ? null : forwardButton.getPreferredSize(); @@ -1994,7 +2286,9 @@ public class FlatTabbedPaneUI // tab area height (maxTabHeight is zero if tab count is zero) int tabAreaHeight = (maxTabHeight > 0) ? maxTabHeight - : Math.max( calculateMaxExtrasHeight(), scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, tabHeight ) ) ); + : Math.max( + Math.max( getLeadingPreferredHeight(), getTrailingPreferredHeight() ), + scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, tabHeight ) ) ); // tab area bounds int tx = insets.left; @@ -2004,54 +2298,56 @@ public class FlatTabbedPaneUI int tw = bounds.width - insets.left - insets.right; int th = tabAreaHeight; + int leadingWidth = getLeadingPreferredWidth(); + int trailingWidth = getTrailingPreferredWidth(); + int availWidth = tw - leadingWidth - trailingWidth - tabAreaInsets.left - tabAreaInsets.right; + int totalTabWidth = (rects.length > 0) ? rectsTotalWidth( leftToRight ) : 0; + + // apply tab area alignment + if( totalTabWidth < availWidth && rects.length > 0 ) { + int diff = availWidth - totalTabWidth; + switch( tabAreaAlignment ) { + case ALIGN_LEADING: + trailingWidth += diff; + break; + + case ALIGN_TRAILING: + leadingWidth += diff; + break; + + case ALIGN_CENTER: + leadingWidth += diff / 2; + trailingWidth += diff - (diff / 2); + break; + + case ALIGN_FILL: + stretchTabsWidth( diff, leftToRight ); + totalTabWidth = rectsTotalWidth( leftToRight ); + break; + } + } else if( rects.length == 0 ) + trailingWidth = tw - leadingWidth; + // layout left component Container leftComponent = leftToRight ? leadingComponent : trailingComponent; - if( leftComponent != null ) { - int leftWidth = leftComponent.getPreferredSize().width; - if( !leftToRight ) { - // trailing component fills all available horizontal space - int leadingWidth = (leadingComponent != null) ? leadingComponent.getPreferredSize().width : 0; - leftWidth = Math.max( leftWidth, (rects.length > 0) - ? tw - leadingWidth - (rects[0].x + rects[0].width - rects[rects.length-1].x) - - (tabAreaInsets.left + tabAreaInsets.right) - : tw - leadingWidth ); - } + int leftWidth = leftToRight ? leadingWidth : trailingWidth; + if( leftComponent != null ) leftComponent.setBounds( tx, ty, leftWidth, th ); - // reduce tab area bounds - tx += leftWidth; - tw -= leftWidth; - } - // layout right component Container rightComponent = leftToRight ? trailingComponent : leadingComponent; - if( rightComponent != null ) { - int rightWidth = rightComponent.getPreferredSize().width; - if( leftToRight ) { - // trailing component fills all available horizontal space - rightWidth = Math.max( rightWidth, (rects.length > 0) - ? tw - (rects[rects.length-1].x + rects[rects.length-1].width) - - (tabAreaInsets.left + tabAreaInsets.right) - : tw ); - } + int rightWidth = leftToRight ? trailingWidth : leadingWidth; + if( rightComponent != null ) rightComponent.setBounds( tx + tw - rightWidth, ty, rightWidth, th ); - // reduce tab area bounds - tw -= rightWidth; - } - // layout tab viewport and buttons if( rects.length > 0 ) { - int txi = tx + (leftToRight ? tabAreaInsets.left : tabAreaInsets.right); - int twi = tw - tabAreaInsets.left - tabAreaInsets.right; + int txi = tx + leftWidth + (leftToRight ? tabAreaInsets.left : tabAreaInsets.right); + int twi = tw - leftWidth - rightWidth - tabAreaInsets.left - tabAreaInsets.right; // layout viewport and buttons int viewportWidth = twi; - Rectangle lastRect = rects[rects.length - 1]; - int tabsWidth = leftToRight - ? (lastRect.x + lastRect.width) - : (rects[0].x + rects[0].width - lastRect.x); - if( viewportWidth < tabsWidth ) { + if( viewportWidth < totalTabWidth ) { // need buttons buttonsVisible = true; int buttonsWidth = useMoreButton ? moreButtonSize.width : (backwardButtonSize.width + forwardButtonSize.width); @@ -2077,7 +2373,9 @@ public class FlatTabbedPaneUI } } else { // LEFT and RIGHT tab placement // tab area width (maxTabWidth is zero if tab count is zero) - int tabAreaWidth = (maxTabWidth > 0) ? maxTabWidth : calculateMaxExtrasWidth(); + int tabAreaWidth = (maxTabWidth > 0) + ? maxTabWidth + : Math.max( getLeadingPreferredWidth(), getTrailingPreferredWidth() ); // tab area bounds int tx = (tabPlacement == LEFT) @@ -2087,40 +2385,52 @@ public class FlatTabbedPaneUI int tw = tabAreaWidth; int th = bounds.height - insets.top - insets.bottom; + int topHeight = getLeadingPreferredHeight(); + int bottomHeight = getTrailingPreferredHeight(); + int availHeight = th - topHeight - bottomHeight - tabAreaInsets.top - tabAreaInsets.bottom; + int totalTabHeight = (rects.length > 0) ? rectsTotalHeight() : 0; + + // apply tab area alignment + if( totalTabHeight < availHeight && rects.length > 0 ) { + int diff = availHeight - totalTabHeight; + switch( tabAreaAlignment ) { + case ALIGN_LEADING: + bottomHeight += diff; + break; + + case ALIGN_TRAILING: + topHeight += diff; + break; + + case ALIGN_CENTER: + topHeight += diff / 2; + bottomHeight += diff - (diff / 2); + break; + + case ALIGN_FILL: + stretchTabsHeight( diff ); + totalTabHeight = rectsTotalHeight(); + break; + } + } else if( rects.length == 0 ) + bottomHeight = th - topHeight; + // layout top component - if( leadingComponent != null ) { - int topHeight = leadingComponent.getPreferredSize().height; + if( leadingComponent != null ) leadingComponent.setBounds( tx, ty, tw, topHeight ); - // reduce tab area bounds - ty += topHeight; - th -= topHeight; - } - // layout bottom component - if( trailingComponent != null ) { - int bottomHeight = trailingComponent.getPreferredSize().height; - // trailing component fills all available vertical space - bottomHeight = Math.max( bottomHeight, (rects.length > 0) - ? th - (rects[rects.length-1].y + rects[rects.length-1].height) - - (tabAreaInsets.top + tabAreaInsets.bottom) - : th ); + if( trailingComponent != null ) trailingComponent.setBounds( tx, ty + th - bottomHeight, tw, bottomHeight ); - // reduce tab area bounds - th -= bottomHeight; - } - // layout tab viewport and buttons if( rects.length > 0 ) { - int tyi = ty + tabAreaInsets.top; - int thi = th - tabAreaInsets.top - tabAreaInsets.bottom; + int tyi = ty + topHeight + tabAreaInsets.top; + int thi = th - topHeight - bottomHeight - tabAreaInsets.top - tabAreaInsets.bottom; // layout viewport and buttons int viewportHeight = thi; - Rectangle lastRect = rects[rects.length - 1]; - int tabsHeight = lastRect.y + lastRect.height; - if( viewportHeight < tabsHeight ) { + if( viewportHeight < totalTabHeight ) { // need buttons buttonsVisible = true; int buttonsHeight = useMoreButton ? moreButtonSize.height : (backwardButtonSize.height + forwardButtonSize.height); diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index 6a05180c..8a184861 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -551,6 +551,7 @@ TabbedPane.disabledForeground=@disabledText TabbedPane.shadow=@background TabbedPane.contentBorderInsets=null TabbedPane.hiddenTabsNavigation=moreTabsButton +TabbedPane.tabAreaAlignment=leading TabbedPane.closeIcon=com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon TabbedPane.closeSize=16,16 diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt index 5308a7a0..5ef5e6a6 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -937,6 +937,7 @@ TabbedPane.selectedTabPadInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI TabbedPane.selectionFollowsFocus true TabbedPane.shadow #3c3f41 javax.swing.plaf.ColorUIResource [UI] TabbedPane.showTabSeparators false +TabbedPane.tabAreaAlignment leading TabbedPane.tabAreaInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.tabHeight 32 TabbedPane.tabInsets 4,12,4,12 javax.swing.plaf.InsetsUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt index df5e628a..cb2585b2 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -942,6 +942,7 @@ TabbedPane.selectedTabPadInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI TabbedPane.selectionFollowsFocus true TabbedPane.shadow #f2f2f2 javax.swing.plaf.ColorUIResource [UI] TabbedPane.showTabSeparators false +TabbedPane.tabAreaAlignment leading TabbedPane.tabAreaInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.tabHeight 32 TabbedPane.tabInsets 4,12,4,12 javax.swing.plaf.InsetsUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt index aab155aa..1c29e8dd 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -551,7 +551,7 @@ MenuItem.textAcceleratorGap 24 MenuItem.textNoAcceleratorGap 6 MenuItem.underlineSelectionBackground #e6e6e6 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionCheckBackground #ccccff javax.swing.plaf.ColorUIResource [UI] -MenuItem.underlineSelectionColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] +MenuItem.underlineSelectionColor #ffff00 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionHeight 3 @@ -909,7 +909,7 @@ TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTab TabbedPane.closePressedBackground #00ff00 javax.swing.plaf.ColorUIResource [UI] TabbedPane.closePressedForeground #000000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI] -TabbedPane.contentAreaColor #bbbbbb javax.swing.plaf.ColorUIResource [UI] +TabbedPane.contentAreaColor #ff0000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentOpaque true TabbedPane.contentSeparatorHeight 1 TabbedPane.darkShadow #696969 javax.swing.plaf.ColorUIResource [UI] @@ -932,6 +932,7 @@ TabbedPane.selectedTabPadInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI TabbedPane.selectionFollowsFocus true TabbedPane.shadow #ccffcc javax.swing.plaf.ColorUIResource [UI] TabbedPane.showTabSeparators false +TabbedPane.tabAreaAlignment leading TabbedPane.tabAreaInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.tabHeight 32 TabbedPane.tabInsets 4,12,4,12 javax.swing.plaf.InsetsUIResource [UI] @@ -942,7 +943,7 @@ TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabsOpaque true TabbedPane.tabsOverlapBorder false TabbedPane.textIconGap 4 -TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.underlineColor #ffff00 javax.swing.plaf.ColorUIResource [UI] TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI @@ -1137,7 +1138,7 @@ ToggleButton.tab.disabledUnderlineColor #7a7a7a javax.swing.plaf.ColorUIResou ToggleButton.tab.focusBackground #dddddd javax.swing.plaf.ColorUIResource [UI] ToggleButton.tab.hoverBackground #eeeeee javax.swing.plaf.ColorUIResource [UI] ToggleButton.tab.selectedBackground #00ff00 javax.swing.plaf.ColorUIResource [UI] -ToggleButton.tab.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] +ToggleButton.tab.underlineColor #ffff00 javax.swing.plaf.ColorUIResource [UI] ToggleButton.tab.underlineHeight 2 ToggleButton.textIconGap 4 ToggleButton.textShiftOffset 0 diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java index 79ebdb5b..3fb655ff 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java @@ -112,6 +112,8 @@ public class FlatContainerTest } customTabsChanged( tabbedPane ); + tabBackForegroundChanged( tabbedPane ); + setTabIcons( tabbedPane ); } private void addTab( JTabbedPane tabbedPane ) { @@ -154,24 +156,44 @@ public class FlatContainerTest } private void tabIconsChanged() { - boolean showTabIcons = tabIconsCheckBox.isSelected(); + setTabIcons( tabbedPane1 ); + setTabIcons( tabbedPane2 ); + setTabIcons( tabbedPane3 ); + setTabIcons( tabbedPane4 ); - setTabIcons( tabbedPane1, showTabIcons ); - setTabIcons( tabbedPane2, showTabIcons ); - setTabIcons( tabbedPane3, showTabIcons ); - setTabIcons( tabbedPane4, showTabIcons ); - - tabIconSizeSpinner.setEnabled( showTabIcons ); + tabIconSizeSpinner.setEnabled( tabIconsCheckBox.isSelected() ); } - private void setTabIcons( JTabbedPane tabbedPane, boolean showTabIcons ) { + private void setTabIcons( JTabbedPane tabbedPane ) { + boolean showTabIcons = tabIconsCheckBox.isSelected(); Object iconSize = tabIconSizeSpinner.getValue(); - Icon icon = showTabIcons - ? new ScaledImageIcon( new ImageIcon( getClass().getResource( "/com/formdev/flatlaf/testing/test" + iconSize + ".png" ) ) ) - : null; - tabbedPane.setIconAt( 0, icon ); - tabbedPane.setIconAt( 1, icon ); + Icon icon = null; + Icon disabledIcon = null; + if( showTabIcons ) { + ImageIcon imageIcon = new ImageIcon( getClass().getResource( "/com/formdev/flatlaf/testing/test" + iconSize + ".png" ) ); + icon = new ScaledImageIcon( imageIcon ); + disabledIcon = UIManager.getLookAndFeel().getDisabledIcon( tabbedPane, imageIcon ); + if( disabledIcon instanceof ImageIcon ) + disabledIcon = new ScaledImageIcon( (ImageIcon) disabledIcon ); + } + + int tabCount = tabbedPane.getTabCount(); + for( int i = 0; i < tabCount; i++ ) { + tabbedPane.setIconAt( i, icon ); + tabbedPane.setDisabledIconAt( i, disabledIcon ); + } + } + + private void iconPlacementChanged() { + Object iconPlacement = null; + switch( (String) iconPlacementField.getSelectedItem() ) { + case "leading": iconPlacement = SwingConstants.LEADING; break; + case "trailing": iconPlacement = SwingConstants.TRAILING; break; + case "top": iconPlacement = SwingConstants.TOP; break; + case "bottom": iconPlacement = SwingConstants.BOTTOM; break; + } + putTabbedPanesClientProperty( TABBED_PANE_TAB_ICON_PLACEMENT, iconPlacement ); } private void customBorderChanged() { @@ -195,11 +217,11 @@ public class FlatContainerTest private void customTabsChanged( JTabbedPane tabbedPane ) { boolean customTabs = customTabsCheckBox.isSelected(); int tabCount = tabbedPane.getTabCount(); - if( tabCount >= 2 ) + if( tabCount > 1 ) tabbedPane.setTabComponentAt( 1, customTabs ? new JButton( tabbedPane.getTitleAt( 1 ) ) : null ); - if( tabCount >= 4 ) + if( tabCount > 3 ) tabbedPane.setTabComponentAt( 3, customTabs ? createCustomTab( tabbedPane.getTitleAt( 3 ) ) : null ); - if( tabCount >= 6 ) + if( tabCount > 5 ) tabbedPane.setTabComponentAt( 5, customTabs ? new JCheckBox( tabbedPane.getTitleAt( 5 ) ) : null ); } @@ -219,6 +241,26 @@ public class FlatContainerTest return tab; } + private void htmlTabsChanged() { + htmlTabsChanged( tabbedPane1 ); + htmlTabsChanged( tabbedPane2 ); + htmlTabsChanged( tabbedPane3 ); + htmlTabsChanged( tabbedPane4 ); + } + + private void htmlTabsChanged( JTabbedPane tabbedPane ) { + boolean html = htmlTabsCheckBox.isSelected(); + boolean multiLine = multiLineTabsCheckBox.isSelected(); + String s = multiLine + ? "Bold Tab
Second Line " + : (html ? "Bold Tab " : "Tab "); + int tabCount = tabbedPane.getTabCount(); + if( tabCount > 0 ) + tabbedPane.setTitleAt( 0, s + "1" ); + if( tabCount > 3 ) + tabbedPane.setTitleAt( 3, s + "4" ); + } + private void tabPlacementChanged() { int tabPlacement = -1; switch( (String) tabPlacementField.getSelectedItem() ) { @@ -235,19 +277,40 @@ public class FlatContainerTest } private void hiddenTabsNavigationChanged() { - String value = null; - switch( (String) hiddenTabsNavigationField.getSelectedItem() ) { - case "moreTabsButton": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON; break; - case "arrowButtons": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS; break; - } - + String value = (String) hiddenTabsNavigationField.getSelectedItem(); + if( "default".equals( value ) ) + value = null; putTabbedPanesClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); } + private void tabAreaAlignmentChanged() { + String value = (String) tabAreaAlignmentField.getSelectedItem(); + if( "default".equals( value ) ) + value = null; + 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 ); + tabBackForegroundChanged( tabbedPane3 ); + tabBackForegroundChanged( tabbedPane4 ); + } + + private void tabBackForegroundChanged( JTabbedPane tabbedPane ) { boolean enabled = tabBackForegroundCheckBox.isSelected(); - tabbedPane1.setBackgroundAt( 0, enabled ? Color.red : null ); - tabbedPane1.setForegroundAt( 1, enabled ? Color.red : null ); + int tabCount = tabbedPane.getTabCount(); + if( tabCount > 0 ) + tabbedPane.setBackgroundAt( 0, enabled ? Color.red : null ); + if( tabCount > 1 ) + tabbedPane.setForegroundAt( 1, enabled ? Color.red : null ); } private void leadingComponentChanged() { @@ -293,14 +356,16 @@ public class FlatContainerTest JTabbedPane[] tabbedPanes = new JTabbedPane[] { tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 }; for( JTabbedPane tabbedPane : tabbedPanes ) { - Component c = tabbedPane.getComponentAt( 1 ); - ((JComponent)c).putClientProperty( TABBED_PANE_TAB_CLOSABLE, value ); + if( tabbedPane.getTabCount() > 1 ) { + Component c = tabbedPane.getComponentAt( 1 ); + ((JComponent)c).putClientProperty( TABBED_PANE_TAB_CLOSABLE, value ); + } } } private void tabAreaInsetsChanged() { - UIManager.put( "TabbedPane.tabAreaInsets", tabAreaInsetsCheckBox.isSelected() ? new Insets( 10, 10, 25, 25 ) : null ); - FlatLaf.updateUI(); + Insets insets = tabAreaInsetsCheckBox.isSelected() ? new Insets( 5, 5, 10, 10 ) : null; + putTabbedPanesClientProperty( TABBED_PANE_TAB_AREA_INSETS, insets ); } private void smallerTabHeightChanged() { @@ -318,11 +383,23 @@ public class FlatContainerTest JTabbedPane[] tabbedPanes = new JTabbedPane[] { tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 }; for( JTabbedPane tabbedPane : tabbedPanes ) { - Component c = tabbedPane.getComponentAt( 1 ); - ((JComponent)c).putClientProperty( TABBED_PANE_TAB_INSETS, insets ); + if( tabbedPane.getTabCount() > 1 ) { + Component c = tabbedPane.getComponentAt( 1 ); + ((JComponent)c).putClientProperty( TABBED_PANE_TAB_INSETS, insets ); + } } } + 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(); @@ -346,6 +423,8 @@ public class FlatContainerTest JLabel tabCountLabel = new JLabel(); tabCountSpinner = new JSpinner(); customTabsCheckBox = new JCheckBox(); + htmlTabsCheckBox = new JCheckBox(); + multiLineTabsCheckBox = new JCheckBox(); JLabel hiddenTabsNavigationLabel = new JLabel(); hiddenTabsNavigationField = new JComboBox<>(); tabBackForegroundCheckBox = new JCheckBox(); @@ -353,6 +432,11 @@ public class FlatContainerTest tabPlacementField = new JComboBox<>(); tabIconsCheckBox = new JCheckBox(); tabIconSizeSpinner = new JSpinner(); + iconPlacementField = new JComboBox<>(); + JLabel tabAreaAlignmentLabel = new JLabel(); + tabAreaAlignmentField = new JComboBox<>(); + JLabel tabWidthModeLabel = new JLabel(); + tabWidthModeField = new JComboBox<>(); tabsClosableCheckBox = new JCheckBox(); customBorderCheckBox = new JCheckBox(); tabAreaInsetsCheckBox = new JCheckBox(); @@ -365,6 +449,8 @@ public class FlatContainerTest trailingComponentCheckBox = new JCheckBox(); showTabSeparatorsCheckBox = new JCheckBox(); secondTabWiderCheckBox = new JCheckBox(); + minimumTabWidthCheckBox = new JCheckBox(); + maximumTabWidthCheckBox = new JCheckBox(); CellConstraints cc = new CellConstraints(); //======== this ======== @@ -476,6 +562,9 @@ public class FlatContainerTest // rows "[center]" + "[]" + + "[]" + + "[]para" + + "[]" + "[]para" + "[]" + "[]para" + @@ -502,6 +591,16 @@ public class FlatContainerTest customTabsCheckBox.addActionListener(e -> customTabsChanged()); tabbedPaneControlPanel.add(customTabsCheckBox, "cell 2 0"); + //---- htmlTabsCheckBox ---- + htmlTabsCheckBox.setText("HTML"); + htmlTabsCheckBox.addActionListener(e -> htmlTabsChanged()); + tabbedPaneControlPanel.add(htmlTabsCheckBox, "cell 2 0"); + + //---- multiLineTabsCheckBox ---- + multiLineTabsCheckBox.setText("multi-line"); + multiLineTabsCheckBox.addActionListener(e -> htmlTabsChanged()); + tabbedPaneControlPanel.add(multiLineTabsCheckBox, "cell 2 0"); + //---- hiddenTabsNavigationLabel ---- hiddenTabsNavigationLabel.setText("Hidden tabs navigation:"); tabbedPaneControlPanel.add(hiddenTabsNavigationLabel, "cell 0 1"); @@ -546,65 +645,114 @@ public class FlatContainerTest tabIconSizeSpinner.addChangeListener(e -> tabIconsChanged()); tabbedPaneControlPanel.add(tabIconSizeSpinner, "cell 2 2"); + //---- iconPlacementField ---- + iconPlacementField.setModel(new DefaultComboBoxModel<>(new String[] { + "leading", + "trailing", + "top", + "bottom" + })); + iconPlacementField.addActionListener(e -> iconPlacementChanged()); + tabbedPaneControlPanel.add(iconPlacementField, "cell 2 2"); + + //---- tabAreaAlignmentLabel ---- + tabAreaAlignmentLabel.setText("Tab area alignment:"); + tabbedPaneControlPanel.add(tabAreaAlignmentLabel, "cell 0 3"); + + //---- tabAreaAlignmentField ---- + tabAreaAlignmentField.setModel(new DefaultComboBoxModel<>(new String[] { + "default", + "leading", + "trailing", + "center", + "fill" + })); + 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()); - tabbedPaneControlPanel.add(tabsClosableCheckBox, "cell 0 3"); + tabbedPaneControlPanel.add(tabsClosableCheckBox, "cell 0 4"); //---- customBorderCheckBox ---- customBorderCheckBox.setText("Custom border"); customBorderCheckBox.addActionListener(e -> customBorderChanged()); - tabbedPaneControlPanel.add(customBorderCheckBox, "cell 1 3"); + tabbedPaneControlPanel.add(customBorderCheckBox, "cell 1 4"); //---- tabAreaInsetsCheckBox ---- - tabAreaInsetsCheckBox.setText("Tab area insets (10,10,25,25)"); + tabAreaInsetsCheckBox.setText("Tab area insets (5,5,10,10)"); tabAreaInsetsCheckBox.addActionListener(e -> tabAreaInsetsChanged()); - tabbedPaneControlPanel.add(tabAreaInsetsCheckBox, "cell 2 3"); + tabbedPaneControlPanel.add(tabAreaInsetsCheckBox, "cell 2 4"); //---- secondTabClosableCheckBox ---- secondTabClosableCheckBox.setText("Second Tab closable"); secondTabClosableCheckBox.addActionListener(e -> secondTabClosableChanged()); - tabbedPaneControlPanel.add(secondTabClosableCheckBox, "cell 0 4"); + tabbedPaneControlPanel.add(secondTabClosableCheckBox, "cell 0 5"); //---- hasFullBorderCheckBox ---- hasFullBorderCheckBox.setText("Show content border"); hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged()); - tabbedPaneControlPanel.add(hasFullBorderCheckBox, "cell 1 4,alignx left,growx 0"); + tabbedPaneControlPanel.add(hasFullBorderCheckBox, "cell 1 5,alignx left,growx 0"); //---- smallerTabHeightCheckBox ---- smallerTabHeightCheckBox.setText("Smaller tab height (26)"); smallerTabHeightCheckBox.addActionListener(e -> smallerTabHeightChanged()); - tabbedPaneControlPanel.add(smallerTabHeightCheckBox, "cell 2 4"); + tabbedPaneControlPanel.add(smallerTabHeightCheckBox, "cell 2 5"); //---- leadingComponentCheckBox ---- leadingComponentCheckBox.setText("Leading component"); leadingComponentCheckBox.addActionListener(e -> leadingComponentChanged()); - tabbedPaneControlPanel.add(leadingComponentCheckBox, "cell 0 5"); + tabbedPaneControlPanel.add(leadingComponentCheckBox, "cell 0 6"); //---- hideContentSeparatorCheckBox ---- hideContentSeparatorCheckBox.setText("Hide content separator"); hideContentSeparatorCheckBox.addActionListener(e -> hideContentSeparatorChanged()); - tabbedPaneControlPanel.add(hideContentSeparatorCheckBox, "cell 1 5"); + tabbedPaneControlPanel.add(hideContentSeparatorCheckBox, "cell 1 6"); //---- smallerInsetsCheckBox ---- smallerInsetsCheckBox.setText("Smaller tab insets (2,2,2,2)"); smallerInsetsCheckBox.addActionListener(e -> smallerInsetsChanged()); - tabbedPaneControlPanel.add(smallerInsetsCheckBox, "cell 2 5"); + tabbedPaneControlPanel.add(smallerInsetsCheckBox, "cell 2 6"); //---- trailingComponentCheckBox ---- trailingComponentCheckBox.setText("Trailing component"); trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged()); - tabbedPaneControlPanel.add(trailingComponentCheckBox, "cell 0 6"); + tabbedPaneControlPanel.add(trailingComponentCheckBox, "cell 0 7"); //---- showTabSeparatorsCheckBox ---- showTabSeparatorsCheckBox.setText("Show tab separators"); showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged()); - tabbedPaneControlPanel.add(showTabSeparatorsCheckBox, "cell 1 6"); + tabbedPaneControlPanel.add(showTabSeparatorsCheckBox, "cell 1 7"); //---- secondTabWiderCheckBox ---- secondTabWiderCheckBox.setText("Second Tab insets wider (4,20,4,20)"); secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged()); - tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 6"); + tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 7"); + + //---- minimumTabWidthCheckBox ---- + minimumTabWidthCheckBox.setText("Minimum tab width (100)"); + minimumTabWidthCheckBox.addActionListener(e -> minimumTabWidthChanged()); + tabbedPaneControlPanel.add(minimumTabWidthCheckBox, "cell 2 8"); + + //---- maximumTabWidthCheckBox ---- + maximumTabWidthCheckBox.setText("Maximum tab width (60)"); + maximumTabWidthCheckBox.addActionListener(e -> maximumTabWidthChanged()); + tabbedPaneControlPanel.add(maximumTabWidthCheckBox, "cell 2 9"); } panel9.add(tabbedPaneControlPanel, cc.xywh(1, 11, 3, 1)); } @@ -620,11 +768,16 @@ public class FlatContainerTest private JCheckBox tabScrollCheckBox; private JSpinner tabCountSpinner; private JCheckBox customTabsCheckBox; + private JCheckBox htmlTabsCheckBox; + private JCheckBox multiLineTabsCheckBox; private JComboBox hiddenTabsNavigationField; private JCheckBox tabBackForegroundCheckBox; private JComboBox tabPlacementField; private JCheckBox tabIconsCheckBox; private JSpinner tabIconSizeSpinner; + private JComboBox iconPlacementField; + private JComboBox tabAreaAlignmentField; + private JComboBox tabWidthModeField; private JCheckBox tabsClosableCheckBox; private JCheckBox customBorderCheckBox; private JCheckBox tabAreaInsetsCheckBox; @@ -637,6 +790,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 ---------------------------------------------------- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd index 789f156b..2fb4ea68 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd @@ -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 @@ -176,6 +176,26 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 2 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "htmlTabsCheckBox" + "text": "HTML" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "htmlTabsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "multiLineTabsCheckBox" + "text": "multi-line" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "htmlTabsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "hiddenTabsNavigationLabel" "text": "Hidden tabs navigation:" @@ -261,6 +281,70 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 2 2" } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "iconPlacementField" + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "leading" + addElement( "leading" ) + addElement( "trailing" ) + addElement( "top" ) + addElement( "bottom" ) + } + auxiliary() { + "JavaCodeGenerator.variableLocal": false + "JavaCodeGenerator.typeParameters": "String" + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconPlacementChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 2" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "tabAreaAlignmentLabel" + "text": "Tab area alignment:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "tabAreaAlignmentField" + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "default" + addElement( "default" ) + addElement( "leading" ) + addElement( "trailing" ) + addElement( "center" ) + addElement( "fill" ) + } + auxiliary() { + "JavaCodeGenerator.variableLocal": false + "JavaCodeGenerator.typeParameters": "String" + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabAreaAlignmentChanged", false ) ) + }, 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" @@ -269,7 +353,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabsClosableChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3" + "value": "cell 0 4" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "customBorderCheckBox" @@ -279,17 +363,17 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "customBorderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 3" + "value": "cell 1 4" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "tabAreaInsetsCheckBox" - "text": "Tab area insets (10,10,25,25)" + "text": "Tab area insets (5,5,10,10)" auxiliary() { "JavaCodeGenerator.variableLocal": false } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabAreaInsetsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 3" + "value": "cell 2 4" } ) add( new FormComponent( "com.formdev.flatlaf.extras.TriStateCheckBox" ) { name: "secondTabClosableCheckBox" @@ -299,7 +383,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabClosableChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4" + "value": "cell 0 5" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hasFullBorderCheckBox" @@ -309,7 +393,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hasFullBorderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4,alignx left,growx 0" + "value": "cell 1 5,alignx left,growx 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "smallerTabHeightCheckBox" @@ -319,7 +403,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerTabHeightChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 4" + "value": "cell 2 5" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "leadingComponentCheckBox" @@ -329,7 +413,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "leadingComponentChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 5" + "value": "cell 0 6" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hideContentSeparatorCheckBox" @@ -339,7 +423,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideContentSeparatorChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 5" + "value": "cell 1 6" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "smallerInsetsCheckBox" @@ -349,7 +433,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerInsetsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 5" + "value": "cell 2 6" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "trailingComponentCheckBox" @@ -359,7 +443,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "trailingComponentChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6" + "value": "cell 0 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "showTabSeparatorsCheckBox" @@ -369,7 +453,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTabSeparatorsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 6" + "value": "cell 1 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "secondTabWiderCheckBox" @@ -379,7 +463,27 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabWiderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 6" + "value": "cell 2 7" + } ) + 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 8" + } ) + 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 9" } ) }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { "gridY": 11 @@ -390,7 +494,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 810, 745 ) + "size": new java.awt.Dimension( 810, 860 ) } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3,align center center" @@ -420,7 +524,7 @@ new FormModel { "value": "cell 2 0" } ) }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 0, 790 ) + "location": new java.awt.Point( 0, 890 ) "size": new java.awt.Dimension( 291, 118 ) } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { @@ -445,7 +549,7 @@ new FormModel { "value": "cell 1 0" } ) }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 340, 790 ) + "location": new java.awt.Point( 340, 890 ) "size": new java.awt.Dimension( 291, 118 ) } ) } diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties index e508b905..109fb330 100644 --- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties +++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties @@ -277,12 +277,12 @@ SplitPaneDivider.oneTouchHoverArrowColor=#f00 TabbedPane.disabledForeground=#777 TabbedPane.selectedBackground=#0f0 TabbedPane.selectedForeground=#00f -TabbedPane.underlineColor=#4A88C7 +TabbedPane.underlineColor=#ff0 TabbedPane.disabledUnderlineColor=#7a7a7a TabbedPane.hoverColor=#eee TabbedPane.focusColor=#ddd TabbedPane.tabSeparatorColor=#00f -TabbedPane.contentAreaColor=#bbb +TabbedPane.contentAreaColor=#f00 TabbedPane.closeSize=16,16 TabbedPane.closeArc=999 diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index c6c01066..52c3803d 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -667,6 +667,7 @@ TabbedPane.selectedTabPadInsets TabbedPane.selectionFollowsFocus TabbedPane.shadow TabbedPane.showTabSeparators +TabbedPane.tabAreaAlignment TabbedPane.tabAreaInsets TabbedPane.tabHeight TabbedPane.tabInsets