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