Merge pull request #193 into master

TabbedPane closable tabs
This commit is contained in:
Karl Tauber
2020-10-22 22:40:30 +02:00
15 changed files with 752 additions and 36 deletions

View File

@@ -16,6 +16,7 @@ FlatLaf Change Log
- TabbedPane: Support adding custom components to left and right sides of tabs - TabbedPane: Support adding custom components to left and right sides of tabs
area. (set client property `JTabbedPane.leadingComponent` or area. (set client property `JTabbedPane.leadingComponent` or
`JTabbedPane.trailingComponent` to a `java.awt.Component`) (issue #40) `JTabbedPane.trailingComponent` to a `java.awt.Component`) (issue #40)
- TabbedPane: Support closable tabs. (issues #31 and #40)
- Support painting separator line between window title and content (use UI value - Support painting separator line between window title and content (use UI value
`TitlePane.borderColor`). (issue #184) `TitlePane.borderColor`). (issue #184)

View File

@@ -25,6 +25,8 @@ import javax.swing.JComponent;
*/ */
public interface FlatClientProperties public interface FlatClientProperties
{ {
//---- JButton ------------------------------------------------------------
/** /**
* Specifies type of a button. * Specifies type of a button.
* <p> * <p>
@@ -104,6 +106,8 @@ public interface FlatClientProperties
*/ */
String SQUARE_SIZE = "JButton.squareSize"; String SQUARE_SIZE = "JButton.squareSize";
//---- JComponent ---------------------------------------------------------
/** /**
* Specifies minimum width of a component. * Specifies minimum width of a component.
* <p> * <p>
@@ -158,6 +162,8 @@ public interface FlatClientProperties
*/ */
String COMPONENT_ROUND_RECT = "JComponent.roundRect"; String COMPONENT_ROUND_RECT = "JComponent.roundRect";
//---- Popup --------------------------------------------------------------
/** /**
* Specifies whether a drop shadow is painted if the component is shown in a popup * Specifies whether a drop shadow is painted if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup. * or if the component is the owner of another component that is shown in a popup.
@@ -167,6 +173,8 @@ public interface FlatClientProperties
*/ */
String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"; String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted";
//---- JProgressBar -------------------------------------------------------
/** /**
* Specifies whether the progress bar has always the larger height even if no string is painted. * Specifies whether the progress bar has always the larger height even if no string is painted.
* <p> * <p>
@@ -183,6 +191,8 @@ public interface FlatClientProperties
*/ */
String PROGRESS_BAR_SQUARE = "JProgressBar.square"; String PROGRESS_BAR_SQUARE = "JProgressBar.square";
//---- JRootPane ----------------------------------------------------------
/** /**
* Specifies whether the menu bar is embedded into the title pane if custom * Specifies whether the menu bar is embedded into the title pane if custom
* window decorations are enabled. Default is {@code true}. * window decorations are enabled. Default is {@code true}.
@@ -192,6 +202,8 @@ public interface FlatClientProperties
*/ */
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
//---- JScrollBar ---------------------------------------------------------
/** /**
* Specifies whether the decrease/increase arrow buttons of a scrollbar are shown. * Specifies whether the decrease/increase arrow buttons of a scrollbar are shown.
* <p> * <p>
@@ -208,6 +220,8 @@ public interface FlatClientProperties
*/ */
String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling"; String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling";
//---- JTabbedPane --------------------------------------------------------
/** /**
* Specifies whether separators are shown between tabs. * Specifies whether separators are shown between tabs.
* <p> * <p>
@@ -240,6 +254,78 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"; String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight";
/**
* Specifies the insets of a tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.awt.Insets}
*/
String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets";
/**
* Specifies whether tabs are closable.
* If set to {@code true} on a tabbed pane component, all tabs in that tabbed pane are closable.
* To make individual tabs closable, set it to {@code true} on a tab content component.
* <p>
* Note that you have to specify a callback (see {@link #TABBED_PANE_TAB_CLOSABLE})
* that is invoked when the user clicks a tab close button.
* The callback is responsible for closing the tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @see #TABBED_PANE_TAB_CLOSE_CALLBACK
*/
String TABBED_PANE_TAB_CLOSABLE = "JTabbedPane.tabClosable";
/**
* Specifies the tooltip text used for tab close buttons.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.String}
*/
String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JTabbedPane.tabCloseToolTipText";
/**
* Specifies the callback that is invoked when a tab close button is clicked.
* The callback is responsible for closing the tab.
* <p>
* Either use a {@link java.util.function.IntConsumer} that received the tab index as parameter:
* <pre>{@code
* myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
* (IntConsumer) tabIndex -> {
* // close tab here
* } );
* }</pre>
* Or use a {@link java.util.function.BiConsumer}&lt;javax.swing.JTabbedPane, Integer&gt;
* that received the tabbed pane and the tab index as parameters:
* <pre>{@code
* myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
* (BiConsumer<JTabbedPane, Integer>) (tabbedPane, tabIndex) -> {
* // close tab here
* } );
* }</pre>
* If you need to check whether a modifier key (e.g. Alt or Shift) was pressed
* while the user clicked the tab close button, use {@link java.awt.EventQueue#getCurrentEvent}
* to get current event, check whether it is a {@link java.awt.event.MouseEvent}
* and invoke its methods. E.g.
* <pre>{@code
* AWTEvent e = EventQueue.getCurrentEvent();
* boolean shift = (e instanceof MouseEvent) ? ((MouseEvent)e).isShiftDown() : false;
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.util.function.IntConsumer}
* or {@link java.util.function.BiConsumer}&lt;javax.swing.JTabbedPane, Integer&gt;
*
* @see #TABBED_PANE_TAB_CLOSABLE
*/
String TABBED_PANE_TAB_CLOSE_CALLBACK = "JTabbedPane.tabCloseCallback";
/** /**
* Specifies how to navigate to hidden tabs. * Specifies how to navigate to hidden tabs.
* <p> * <p>
@@ -280,6 +366,8 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent"; String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent";
//---- JTextField ---------------------------------------------------------
/** /**
* Specifies whether all text is selected when the text component gains focus. * Specifies whether all text is selected when the text component gains focus.
* <p> * <p>
@@ -322,6 +410,8 @@ public interface FlatClientProperties
*/ */
String PLACEHOLDER_TEXT = "JTextField.placeholderText"; String PLACEHOLDER_TEXT = "JTextField.placeholderText";
//---- JToggleButton ------------------------------------------------------
/** /**
* Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}. * Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p> * <p>
@@ -346,6 +436,8 @@ public interface FlatClientProperties
*/ */
String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"; String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground";
//---- helper methods -----------------------------------------------------
/** /**
* Checks whether a client property of a component has the given value. * Checks whether a client property of a component has the given value.
*/ */

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "close" icon for closable tabs in {@link javax.swing.JTabbedPane}.
*
* @uiDefault TabbedPane.closeSize Dimension
* @uiDefault TabbedPane.closeArc int
* @uiDefault TabbedPane.closeCrossPlainSize float
* @uiDefault TabbedPane.closeCrossFilledSize float
* @uiDefault TabbedPane.closeCrossLineWidth float
* @uiDefault TabbedPane.closeBackground Color
* @uiDefault TabbedPane.closeForeground Color
* @uiDefault TabbedPane.closeHoverBackground Color
* @uiDefault TabbedPane.closeHoverForeground Color
* @uiDefault TabbedPane.closePressedBackground Color
* @uiDefault TabbedPane.closePressedForeground Color
*
* @author Karl Tauber
*/
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
{
protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" );
protected final int arc = UIManager.getInt( "TabbedPane.closeArc" );
protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize );
protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" );
protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" );
protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
public FlatTabbedPaneCloseIcon() {
super( 16, 16, null );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background
Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground );
if( bg != null ) {
g.setColor( bg );
g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2,
size.width, size.height, arc, arc );
}
// set cross color
g.setColor( FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground ) );
float mx = width / 2;
float my = height / 2;
float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2;
// paint cross
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( mx - r, my - r, mx + r, my + r ), false );
path.append( new Line2D.Float( mx - r, my + r, mx + r, my - r ), false );
g.setStroke( new BasicStroke( closeCrossLineWidth ) );
g.draw( path );
}
}

View File

@@ -38,11 +38,14 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelEvent;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
@@ -51,6 +54,10 @@ import java.beans.PropertyChangeListener;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.IntConsumer;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
@@ -90,7 +97,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line * @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line
* @uiDefault TabbedPane.textIconGap int * @uiDefault TabbedPane.textIconGap int
* @uiDefault TabbedPane.tabInsets Insets * @uiDefault TabbedPane.tabInsets Insets
* @uiDefault TabbedPane.selectedTabPadInsets Insets * @uiDefault TabbedPane.selectedTabPadInsets Insets unused
* @uiDefault TabbedPane.tabAreaInsets Insets * @uiDefault TabbedPane.tabAreaInsets Insets
* @uiDefault TabbedPane.tabsOverlapBorder boolean * @uiDefault TabbedPane.tabsOverlapBorder boolean
* @uiDefault TabbedPane.tabRunOverlay int * @uiDefault TabbedPane.tabRunOverlay int
@@ -119,6 +126,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.hasFullBorder boolean * @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons * @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault ScrollPane.smoothScrolling boolean * @uiDefault ScrollPane.smoothScrolling boolean
* @uiDefault TabbedPane.closeIcon Icon
* *
* @uiDefault TabbedPane.moreTabsButtonToolTipText String * @uiDefault TabbedPane.moreTabsButtonToolTipText String
* *
@@ -144,6 +152,7 @@ public class FlatTabbedPaneUI
protected Color tabSeparatorColor; protected Color tabSeparatorColor;
protected Color contentAreaColor; protected Color contentAreaColor;
private int textIconGapUnscaled;
protected int tabHeight; protected int tabHeight;
protected int tabSelectionHeight; protected int tabSelectionHeight;
protected int contentSeparatorHeight; protected int contentSeparatorHeight;
@@ -152,18 +161,22 @@ public class FlatTabbedPaneUI
protected boolean hasFullBorder; protected boolean hasFullBorder;
private String hiddenTabsNavigationStr; private String hiddenTabsNavigationStr;
protected Icon closeIcon;
protected String moreTabsButtonToolTipText; protected String moreTabsButtonToolTipText;
protected JViewport tabViewport; protected JViewport tabViewport;
protected FlatWheelTabScroller wheelTabScroller; protected FlatWheelTabScroller wheelTabScroller;
private JButton tabCloseButton;
private JButton moreTabsButton; private JButton moreTabsButton;
private Container leadingComponent; private Container leadingComponent;
private Container trailingComponent; private Container trailingComponent;
private Handler handler; private Handler handler;
private boolean blockRollover; private boolean blockRollover;
private boolean rolloverTabClose;
private boolean pressedTabClose;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatTabbedPaneUI(); return new FlatTabbedPaneUI();
@@ -203,6 +216,7 @@ public class FlatTabbedPaneUI
tabSeparatorColor = UIManager.getColor( "TabbedPane.tabSeparatorColor" ); tabSeparatorColor = UIManager.getColor( "TabbedPane.tabSeparatorColor" );
contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" ); contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" );
textIconGapUnscaled = UIManager.getInt( "TabbedPane.textIconGap" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" ); tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" ); tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" ); contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
@@ -210,17 +224,13 @@ public class FlatTabbedPaneUI
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" ); tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" ); hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
Locale l = tabPane.getLocale(); Locale l = tabPane.getLocale();
moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l ); moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l );
// scale // scale
textIconGap = scale( textIconGap ); textIconGap = scale( textIconGapUnscaled );
tabInsets = scale( tabInsets );
selectedTabPadInsets = scale( selectedTabPadInsets );
tabAreaInsets = scale( tabAreaInsets );
tabHeight = scale( tabHeight );
tabSelectionHeight = scale( tabSelectionHeight );
// replace focus forward/backward traversal keys with TAB/Shift+TAB because // replace focus forward/backward traversal keys with TAB/Shift+TAB because
// the default also includes Ctrl+TAB/Ctrl+Shift+TAB, which we need to switch tabs // the default also includes Ctrl+TAB/Ctrl+Shift+TAB, which we need to switch tabs
@@ -255,6 +265,8 @@ public class FlatTabbedPaneUI
tabSeparatorColor = null; tabSeparatorColor = null;
contentAreaColor = null; contentAreaColor = null;
closeIcon = null;
MigLayoutVisualPadding.uninstall( tabPane ); MigLayoutVisualPadding.uninstall( tabPane );
} }
@@ -262,6 +274,11 @@ public class FlatTabbedPaneUI
protected void installComponents() { protected void installComponents() {
super.installComponents(); super.installComponents();
// create tab close button
tabCloseButton = new TabCloseButton();
tabCloseButton.setVisible( false );
tabPane.add( tabCloseButton );
// find scrollable tab viewport // find scrollable tab viewport
tabViewport = null; tabViewport = null;
if( isScrollTabLayout() ) { if( isScrollTabLayout() ) {
@@ -288,6 +305,11 @@ public class FlatTabbedPaneUI
super.uninstallComponents(); super.uninstallComponents();
if( tabCloseButton != null ) {
tabPane.remove( tabCloseButton );
tabCloseButton = null;
}
tabViewport = null; tabViewport = null;
} }
@@ -354,9 +376,7 @@ public class FlatTabbedPaneUI
protected void installListeners() { protected void installListeners() {
super.installListeners(); super.installListeners();
tabPane.addMouseListener( getHandler() ); getHandler().installListeners();
tabPane.addMouseMotionListener( getHandler() );
tabPane.addComponentListener( getHandler() );
if( tabViewport != null && (wheelTabScroller = createWheelTabScroller()) != null ) { if( tabViewport != null && (wheelTabScroller = createWheelTabScroller()) != null ) {
// ideally we would add the mouse listeners to the viewport, but then the // ideally we would add the mouse listeners to the viewport, but then the
@@ -373,9 +393,7 @@ public class FlatTabbedPaneUI
super.uninstallListeners(); super.uninstallListeners();
if( handler != null ) { if( handler != null ) {
tabPane.removeMouseListener( handler ); handler.uninstallListeners();
tabPane.removeMouseMotionListener( handler );
tabPane.removeComponentListener( handler );
handler = null; handler = null;
} }
@@ -399,6 +417,13 @@ public class FlatTabbedPaneUI
return new FlatWheelTabScroller(); return new FlatWheelTabScroller();
} }
@Override
protected MouseListener createMouseListener() {
Handler handler = getHandler();
handler.mouseDelegate = super.createMouseListener();
return handler;
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener() { protected PropertyChangeListener createPropertyChangeListener() {
Handler handler = getHandler(); Handler handler = getHandler();
@@ -454,6 +479,30 @@ public class FlatTabbedPaneUI
repaintTab( index ); repaintTab( index );
} }
protected boolean isRolloverTabClose() {
return rolloverTabClose;
}
protected void setRolloverTabClose( boolean rollover ) {
if( rolloverTabClose == rollover )
return;
rolloverTabClose = rollover;
repaintTab( getRolloverTab() );
}
protected boolean isPressedTabClose() {
return pressedTabClose;
}
protected void setPressedTabClose( boolean pressed ) {
if( pressedTabClose == pressed )
return;
pressedTabClose = pressed;
repaintTab( getRolloverTab() );
}
private void repaintTab( int tabIndex ) { private void repaintTab( int tabIndex ) {
if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() ) if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() )
return; return;
@@ -465,15 +514,34 @@ public class FlatTabbedPaneUI
@Override @Override
protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) { protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) {
return super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */; // update textIconGap before used in super class
textIconGap = scale( textIconGapUnscaled );
int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
if( isTabClosable( tabIndex ) )
tabWidth += closeIcon.getIconWidth();
return tabWidth;
} }
@Override @Override
protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) { protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) {
int tabHeight = clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight ); 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 */ ); return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ );
} }
@Override
protected Insets getTabInsets( int tabPlacement, int tabIndex ) {
Object value = getTabClientProperty( tabIndex, TABBED_PANE_TAB_INSETS );
return scale( (value instanceof Insets)
? (Insets) value
: super.getTabInsets( tabPlacement, tabIndex ) );
}
@Override
protected Insets getSelectedTabPadInsets( int tabPlacement ) {
return new Insets( 0, 0, 0, 0 );
}
@Override @Override
protected Insets getTabAreaInsets( int tabPlacement ) { protected Insets getTabAreaInsets( int tabPlacement ) {
Insets currentTabAreaInsets = super.getTabAreaInsets( tabPlacement ); Insets currentTabAreaInsets = super.getTabAreaInsets( tabPlacement );
@@ -486,6 +554,9 @@ public class FlatTabbedPaneUI
// Giving it large values clips painting of the cropped edge and makes it invisible. // Giving it large values clips painting of the cropped edge and makes it invisible.
currentTabAreaInsets.top = currentTabAreaInsets.left = -10000; currentTabAreaInsets.top = currentTabAreaInsets.left = -10000;
// scale insets (before adding leading/trailing component sizes)
insets = scale( insets );
// increase insets for wrap layout if using leading/trailing components // increase insets for wrap layout if using leading/trailing components
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT ) { if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT ) {
if( leadingComponent != null ) { if( leadingComponent != null ) {
@@ -528,6 +599,10 @@ public class FlatTabbedPaneUI
@Override @Override
protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) { protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) {
if( isTabClosable( tabIndex ) ) {
int shift = closeIcon.getIconWidth() / 2;
return isLeftToRight() ? -shift : shift;
}
return 0; return 0;
} }
@@ -619,6 +694,10 @@ public class FlatTabbedPaneUI
protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex, protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex,
int x, int y, int w, int h, boolean isSelected ) int x, int y, int w, int h, boolean isSelected )
{ {
// paint tab close button
if( isTabClosable( tabIndex ) )
paintTabCloseButton( g, tabIndex, x, y, w, h );
// paint tab separators // paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) ) !isLastInRun( tabIndex ) )
@@ -628,6 +707,18 @@ public class FlatTabbedPaneUI
paintTabSelection( g, tabPlacement, x, y, w, h ); paintTabSelection( g, tabPlacement, x, y, w, h );
} }
protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) {
// update state of tab close button
boolean rollover = (tabIndex == getRolloverTab());
ButtonModel bm = tabCloseButton.getModel();
bm.setRollover( rollover && isRolloverTabClose() );
bm.setPressed( rollover && isPressedTabClose() );
// paint tab close icon
Rectangle tabCloseRect = getTabCloseBounds( x, y, w, h, calcRect );
closeIcon.paintIcon( tabCloseButton, g, tabCloseRect.x, tabCloseRect.y );
}
protected void paintTabSeparator( Graphics g, int tabPlacement, int x, int y, int w, int h ) { protected void paintTabSeparator( Graphics g, int tabPlacement, int x, int y, int w, int h ) {
float sepWidth = UIScale.scale( 1f ); float sepWidth = UIScale.scale( 1f );
float offset = tabSeparatorsFullHeight ? 0 : UIScale.scale( 5f ); float offset = tabSeparatorsFullHeight ? 0 : UIScale.scale( 5f );
@@ -636,7 +727,7 @@ public class FlatTabbedPaneUI
if( tabPlacement == LEFT || tabPlacement == RIGHT ) { if( tabPlacement == LEFT || tabPlacement == RIGHT ) {
// paint tab separator at bottom side // paint tab separator at bottom side
((Graphics2D)g).fill( new Rectangle2D.Float( x + offset, y + h - sepWidth, w - (offset * 2), sepWidth ) ); ((Graphics2D)g).fill( new Rectangle2D.Float( x + offset, y + h - sepWidth, w - (offset * 2), sepWidth ) );
} else if( tabPane.getComponentOrientation().isLeftToRight() ) { } else if( isLeftToRight() ) {
// paint tab separator at right side // paint tab separator at right side
((Graphics2D)g).fill( new Rectangle2D.Float( x + w - sepWidth, y + offset, sepWidth, h - (offset * 2) ) ); ((Graphics2D)g).fill( new Rectangle2D.Float( x + w - sepWidth, y + offset, sepWidth, h - (offset * 2) ) );
} else { } else {
@@ -682,6 +773,7 @@ public class FlatTabbedPaneUI
Insets contentInsets = getContentBorderInsets( tabPlacement ); Insets contentInsets = getContentBorderInsets( tabPlacement );
// paint underline selection // paint underline selection
int tabSelectionHeight = scale( this.tabSelectionHeight );
switch( tabPlacement ) { switch( tabPlacement ) {
case TOP: case TOP:
default: default:
@@ -788,6 +880,16 @@ 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 )
{
// update textIconGap before used in super class
textIconGap = scale( textIconGapUnscaled );
super.layoutLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
}
@Override @Override
public int tabForCoordinate( JTabbedPane pane, int x, int y ) { public int tabForCoordinate( JTabbedPane pane, int x, int y ) {
if( moreTabsButton != null ) { if( moreTabsButton != null ) {
@@ -819,6 +921,55 @@ public class FlatTabbedPaneUI
return super.getTabBounds( tabIndex, dest ); return super.getTabBounds( tabIndex, dest );
} }
protected Rectangle getTabCloseBounds( int x, int y, int w, int h, Rectangle dest ) {
dest.width = closeIcon.getIconWidth();
dest.height = closeIcon.getIconHeight();
int xoffset = (h - dest.width) / 2;
int yoffset = (h - dest.height) / 2;
dest.x = isLeftToRight() ? (x + w - xoffset - dest.width) : (x + xoffset);
dest.y = y + yoffset;
return dest;
}
protected Rectangle getTabCloseHitArea( int tabIndex ) {
Rectangle tabRect = getTabBounds( tabPane, tabIndex );
Rectangle tabCloseRect = getTabCloseBounds( tabRect.x, tabRect.y, tabRect.width, tabRect.height, calcRect );
return new Rectangle( tabCloseRect.x, tabRect.y, tabCloseRect.width, tabRect.height );
}
protected boolean isTabClosable( int tabIndex ) {
Object value = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSABLE );
return (value instanceof Boolean) ? (boolean) value : false;
}
@SuppressWarnings( { "unchecked" } )
protected void closeTab( int tabIndex ) {
Object callback = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSE_CALLBACK );
if( callback instanceof IntConsumer )
((IntConsumer)callback).accept( tabIndex );
else if( callback instanceof BiConsumer )
((BiConsumer<JTabbedPane, Integer>)callback).accept( tabPane, tabIndex );
else {
throw new RuntimeException( "Missing tab close callback. "
+ "Set client property 'JTabbedPane.tabCloseCallback' "
+ "to a 'java.util.function.IntConsumer' "
+ "or 'java.util.function.BiConsumer<JTabbedPane, Integer>'" );
}
}
protected Object getTabClientProperty( int tabIndex, String key ) {
if( tabIndex < 0 )
return null;
Component c = tabPane.getComponentAt( tabIndex );
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( key );
if( value != null )
return value;
}
return tabPane.getClientProperty( key );
}
protected void ensureCurrentLayout() { protected void ensureCurrentLayout() {
// since super.ensureCurrentLayout() is private, // since super.ensureCurrentLayout() is private,
// use super.getTabRunCount() as workaround // use super.getTabRunCount() as workaround
@@ -834,6 +985,10 @@ public class FlatTabbedPaneUI
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
} }
private boolean isLeftToRight() {
return tabPane.getComponentOrientation().isLeftToRight();
}
protected boolean isHorizontalTabPlacement() { protected boolean isHorizontalTabPlacement() {
int tabPlacement = tabPane.getTabPlacement(); int tabPlacement = tabPane.getTabPlacement();
return tabPlacement == TOP || tabPlacement == BOTTOM; return tabPlacement == TOP || tabPlacement == BOTTOM;
@@ -914,6 +1069,16 @@ public class FlatTabbedPaneUI
} }
} }
//---- class TabCloseButton -----------------------------------------------
private class TabCloseButton
extends JButton
implements UIResource
{
private TabCloseButton() {
}
}
//---- class ContainerUIResource ------------------------------------------ //---- class ContainerUIResource ------------------------------------------
private class ContainerUIResource private class ContainerUIResource
@@ -1011,9 +1176,8 @@ public class FlatTabbedPaneUI
int buttonWidth = getWidth(); int buttonWidth = getWidth();
int buttonHeight = getHeight(); int buttonHeight = getHeight();
Dimension popupSize = popupMenu.getPreferredSize(); Dimension popupSize = popupMenu.getPreferredSize();
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
int x = leftToRight ? buttonWidth - popupSize.width : 0; int x = isLeftToRight() ? buttonWidth - popupSize.width : 0;
int y = buttonHeight - popupSize.height; int y = buttonHeight - popupSize.height;
switch( tabPane.getTabPlacement() ) { switch( tabPane.getTabPlacement() ) {
default: default:
@@ -1185,8 +1349,7 @@ public class FlatTabbedPaneUI
int y = viewPosition.y; int y = viewPosition.y;
int tabPlacement = tabPane.getTabPlacement(); int tabPlacement = tabPane.getTabPlacement();
if( tabPlacement == TOP || tabPlacement == BOTTOM ) { if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); x += isLeftToRight() ? amount : -amount;
x += leftToRight ? amount : -amount;
x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() ); x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() );
} else { } else {
y += amount; y += amount;
@@ -1360,18 +1523,73 @@ public class FlatTabbedPaneUI
//---- class Handler ------------------------------------------------------ //---- class Handler ------------------------------------------------------
private class Handler private class Handler
extends MouseAdapter implements MouseListener, MouseMotionListener, PropertyChangeListener,
implements PropertyChangeListener, ChangeListener, ComponentListener ChangeListener, ComponentListener, ContainerListener
{ {
MouseListener mouseDelegate;
PropertyChangeListener propertyChangeDelegate; PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate; ChangeListener changeDelegate;
private final PropertyChangeListener contentListener = this::contentPropertyChange;
private int pressedTabIndex = -1;
private int lastTipTabIndex = -1;
private String lastTip;
void installListeners() {
tabPane.addMouseMotionListener( this );
tabPane.addComponentListener( this );
tabPane.addContainerListener( this );
for( Component c : tabPane.getComponents() ) {
if( !(c instanceof UIResource) )
c.addPropertyChangeListener( contentListener );
}
}
void uninstallListeners() {
tabPane.removeMouseMotionListener( this );
tabPane.removeComponentListener( this );
tabPane.removeContainerListener( this );
for( Component c : tabPane.getComponents() ) {
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( contentListener );
}
}
//---- interface MouseListener ---- //---- interface MouseListener ----
@Override
public void mouseClicked( MouseEvent e ) {
mouseDelegate.mouseClicked( e );
}
@Override
public void mousePressed( MouseEvent e ) {
updateRollover( e );
if( !isPressedTabClose() )
mouseDelegate.mousePressed( e );
}
@Override
public void mouseReleased( MouseEvent e ) {
if( isPressedTabClose() ) {
updateRollover( e );
if( pressedTabIndex >= 0 && pressedTabIndex == getRolloverTab() )
closeTab( pressedTabIndex );
} else
mouseDelegate.mouseReleased( e );
pressedTabIndex = -1;
updateRollover( e );
}
@Override @Override
public void mouseEntered( MouseEvent e ) { public void mouseEntered( MouseEvent e ) {
// this is necessary for "more tabs" button // this is necessary for "more tabs" button
setRolloverTab( e.getX(), e.getY() ); updateRollover( e );
} }
@Override @Override
@@ -1379,15 +1597,68 @@ public class FlatTabbedPaneUI
// this event occurs also if mouse is moved to a custom tab component // this event occurs also if mouse is moved to a custom tab component
// that handles mouse events (e.g. a close button) // that handles mouse events (e.g. a close button)
// --> make sure that the tab stays highlighted // --> make sure that the tab stays highlighted
setRolloverTab( e.getX(), e.getY() ); updateRollover( e );
} }
//---- interface MouseMotionListener ---- //---- interface MouseMotionListener ----
@Override
public void mouseDragged( MouseEvent e ) {
updateRollover( e );
}
@Override @Override
public void mouseMoved( MouseEvent e ) { public void mouseMoved( MouseEvent e ) {
// this is necessary for "more tabs" button updateRollover( e );
setRolloverTab( e.getX(), e.getY() ); }
private void updateRollover( MouseEvent e ) {
int x = e.getX();
int y = e.getY();
int tabIndex = tabForCoordinate( tabPane, x, y );
setRolloverTab( tabIndex );
// check whether mouse hit tab close area
boolean hitClose = isTabClosable( tabIndex )
? getTabCloseHitArea( tabIndex ).contains( x, y )
: false;
if( e.getID() == MouseEvent.MOUSE_PRESSED )
pressedTabIndex = hitClose ? tabIndex : -1;
setRolloverTabClose( hitClose );
setPressedTabClose( hitClose && tabIndex == pressedTabIndex );
// update tooltip
if( tabIndex >= 0 && hitClose ) {
Object closeTip = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT );
if( closeTip instanceof String )
setCloseToolTip( tabIndex, (String) closeTip );
else
restoreTabToolTip();
} else
restoreTabToolTip();
}
private void setCloseToolTip( int tabIndex, String closeTip ) {
if( tabIndex == lastTipTabIndex )
return; // closeTip already set
if( tabIndex != lastTipTabIndex )
restoreTabToolTip();
lastTipTabIndex = tabIndex;
lastTip = tabPane.getToolTipTextAt( lastTipTabIndex );
tabPane.setToolTipTextAt( lastTipTabIndex, closeTip );
}
private void restoreTabToolTip() {
if( lastTipTabIndex < 0 )
return;
tabPane.setToolTipTextAt( lastTipTabIndex, lastTip );
lastTip = null;
lastTipTabIndex = -1;
} }
//---- interface PropertyChangeListener ---- //---- interface PropertyChangeListener ----
@@ -1425,7 +1696,9 @@ public class FlatTabbedPaneUI
case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER: case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_TAB_HEIGHT: case TABBED_PANE_TAB_HEIGHT:
case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION: case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); tabPane.repaint();
break; break;
@@ -1459,6 +1732,16 @@ public class FlatTabbedPaneUI
ensureSelectedTabIsVisible(); ensureSelectedTabIsVisible();
} }
protected void contentPropertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case TABBED_PANE_TAB_INSETS:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate();
tabPane.repaint();
break;
}
}
//---- interface ComponentListener ---- //---- interface ComponentListener ----
@Override @Override
@@ -1470,6 +1753,22 @@ public class FlatTabbedPaneUI
@Override public void componentMoved( ComponentEvent e ) {} @Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentShown( ComponentEvent e ) {} @Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {} @Override public void componentHidden( ComponentEvent e ) {}
//---- interface ContainerListener ----
@Override
public void componentAdded( ContainerEvent e ) {
Component c = e.getChild();
if( !(c instanceof UIResource) )
c.addPropertyChangeListener( contentListener );
}
@Override
public void componentRemoved( ContainerEvent e ) {
Component c = e.getChild();
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( contentListener );
}
} }
//---- class FlatTabbedPaneLayout ----------------------------------------- //---- class FlatTabbedPaneLayout -----------------------------------------
@@ -1485,7 +1784,7 @@ public class FlatTabbedPaneUI
Insets insets = tabPane.getInsets(); Insets insets = tabPane.getInsets();
int tabPlacement = tabPane.getTabPlacement(); int tabPlacement = tabPane.getTabPlacement();
Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); Insets tabAreaInsets = getTabAreaInsets( tabPlacement );
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); boolean leftToRight = isLeftToRight();
// layout leading and trailing components in tab area // layout leading and trailing components in tab area
if( tabPlacement == TOP || tabPlacement == BOTTOM ) { if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
@@ -1556,7 +1855,7 @@ public class FlatTabbedPaneUI
* Layout manager used for scroll tab layout policy. * Layout manager used for scroll tab layout policy.
* <p> * <p>
* Although this class delegates all methods to the original layout manager * Although this class delegates all methods to the original layout manager
* {@link BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends * {@code BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends
* {@link BasicTabbedPaneUI.TabbedPaneLayout}, it is necessary that this class * {@link BasicTabbedPaneUI.TabbedPaneLayout}, it is necessary that this class
* also extends {@link TabbedPaneLayout} to avoid a {@code ClassCastException} * also extends {@link TabbedPaneLayout} to avoid a {@code ClassCastException}
* in {@link BasicTabbedPaneUI}.ensureCurrentLayout(). * in {@link BasicTabbedPaneUI}.ensureCurrentLayout().
@@ -1603,7 +1902,7 @@ public class FlatTabbedPaneUI
// for right-to-left always use "more tabs" button for horizontal scrolling // for right-to-left always use "more tabs" button for horizontal scrolling
// because methods scrollForward() and scrollBackward() in class // because methods scrollForward() and scrollBackward() in class
// BasicTabbedPaneUI.ScrollableTabSupport do not work for right-to-left // BasicTabbedPaneUI.ScrollableTabSupport do not work for right-to-left
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); boolean leftToRight = isLeftToRight();
if( !leftToRight && !useMoreButton && isHorizontalTabPlacement() ) if( !leftToRight && !useMoreButton && isHorizontalTabPlacement() )
useMoreButton = true; useMoreButton = true;
@@ -1623,6 +1922,13 @@ public class FlatTabbedPaneUI
if( !useMoreButton && (backwardButton == null || forwardButton == null) ) if( !useMoreButton && (backwardButton == null || forwardButton == null) )
return; // should never occur return; // should never occur
if( rects.length == 0 ) {
moreTabsButton.setVisible( false );
backwardButton.setVisible( false );
forwardButton.setVisible( false );
return;
}
Rectangle bounds = tabPane.getBounds(); Rectangle bounds = tabPane.getBounds();
Insets insets = tabPane.getInsets(); Insets insets = tabPane.getInsets();
int tabPlacement = tabPane.getTabPlacement(); int tabPlacement = tabPane.getTabPlacement();

View File

@@ -258,6 +258,13 @@ TabbedPane.hoverColor=#2e3133
TabbedPane.focusColor=#3d4b5c TabbedPane.focusColor=#3d4b5c
TabbedPane.contentAreaColor=#646464 TabbedPane.contentAreaColor=#646464
TabbedPane.closeBackground=null
TabbedPane.closeForeground=@disabledText
TabbedPane.closeHoverBackground=lighten($TabbedPane.hoverColor,10%)
TabbedPane.closeHoverForeground=@foreground
TabbedPane.closePressedBackground=lighten($TabbedPane.hoverColor,15%)
TabbedPane.closePressedForeground=$TabbedPane.closeHoverForeground
#---- Table ---- #---- Table ----

View File

@@ -552,6 +552,13 @@ TabbedPane.shadow=@background
TabbedPane.contentBorderInsets=null TabbedPane.contentBorderInsets=null
TabbedPane.hiddenTabsNavigation=moreTabsButton TabbedPane.hiddenTabsNavigation=moreTabsButton
TabbedPane.closeIcon=com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon
TabbedPane.closeSize=16,16
TabbedPane.closeArc=4
TabbedPane.closeCrossPlainSize={float}7.5
TabbedPane.closeCrossFilledSize=$TabbedPane.closeCrossPlainSize
TabbedPane.closeCrossLineWidth={float}1
#---- Table ---- #---- Table ----

View File

@@ -270,6 +270,13 @@ TabbedPane.hoverColor=#d9d9d9
TabbedPane.focusColor=#dae4ed TabbedPane.focusColor=#dae4ed
TabbedPane.contentAreaColor=#bfbfbf TabbedPane.contentAreaColor=#bfbfbf
TabbedPane.closeBackground=null
TabbedPane.closeForeground=@disabledText
TabbedPane.closeHoverBackground=darken($TabbedPane.hoverColor,10%)
TabbedPane.closeHoverForeground=@foreground
TabbedPane.closePressedBackground=darken($TabbedPane.hoverColor,15%)
TabbedPane.closePressedForeground=$TabbedPane.closeHoverForeground
#---- Table ---- #---- Table ----

View File

@@ -95,6 +95,19 @@ public class TriStateCheckBox
repaint(); repaint();
} }
public Boolean getValue() {
switch( state ) {
default:
case INDETERMINATE: return null;
case SELECTED: return true;
case UNSELECTED: return false;
}
}
public void setValue( Boolean value ) {
setState( value == null ? State.INDETERMINATE : (value ? State.SELECTED : State.UNSELECTED) );
}
public boolean isThirdStateEnabled() { public boolean isThirdStateEnabled() {
return thirdStateEnabled; return thirdStateEnabled;
} }

View File

@@ -905,6 +905,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ---- #---- TabbedPane ----
TabbedPane.background #3c3f41 javax.swing.plaf.ColorUIResource [UI] TabbedPane.background #3c3f41 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeArc 4
TabbedPane.closeCrossFilledSize 7.5
TabbedPane.closeCrossLineWidth 1.0
TabbedPane.closeCrossPlainSize 7.5
TabbedPane.closeForeground #888888 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverBackground #464b4e javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverForeground #bbbbbb javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI]
TabbedPane.closePressedBackground #52585b javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closePressedForeground #bbbbbb javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI]
TabbedPane.contentAreaColor #646464 javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentAreaColor #646464 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.contentOpaque true TabbedPane.contentOpaque true
TabbedPane.contentSeparatorHeight 1 TabbedPane.contentSeparatorHeight 1

View File

@@ -910,6 +910,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ---- #---- TabbedPane ----
TabbedPane.background #f2f2f2 javax.swing.plaf.ColorUIResource [UI] TabbedPane.background #f2f2f2 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeArc 4
TabbedPane.closeCrossFilledSize 7.5
TabbedPane.closeCrossLineWidth 1.0
TabbedPane.closeCrossPlainSize 7.5
TabbedPane.closeForeground #8c8c8c javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverBackground #c0c0c0 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverForeground #000000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI]
TabbedPane.closePressedBackground #b3b3b3 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closePressedForeground #000000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI]
TabbedPane.contentAreaColor #bfbfbf javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentAreaColor #bfbfbf javax.swing.plaf.ColorUIResource [UI]
TabbedPane.contentOpaque true TabbedPane.contentOpaque true
TabbedPane.contentSeparatorHeight 1 TabbedPane.contentSeparatorHeight 1

View File

@@ -898,6 +898,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ---- #---- TabbedPane ----
TabbedPane.background #ccffcc javax.swing.plaf.ColorUIResource [UI] TabbedPane.background #ccffcc javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeArc 999
TabbedPane.closeCrossFilledSize 6.5
TabbedPane.closeCrossLineWidth 2.0
TabbedPane.closeCrossPlainSize 12.0
TabbedPane.closeForeground #ff0000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverBackground #aa0000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeHoverForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI]
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 #bbbbbb javax.swing.plaf.ColorUIResource [UI]
TabbedPane.contentOpaque true TabbedPane.contentOpaque true
TabbedPane.contentSeparatorHeight 1 TabbedPane.contentSeparatorHeight 1

View File

@@ -18,9 +18,12 @@ package com.formdev.flatlaf.testing;
import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.function.BiConsumer;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.*; import javax.swing.border.*;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.extras.TriStateCheckBox;
import com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon; import com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon;
import com.formdev.flatlaf.util.ScaledImageIcon; import com.formdev.flatlaf.util.ScaledImageIcon;
import com.jgoodies.forms.layout.*; import com.jgoodies.forms.layout.*;
@@ -49,6 +52,10 @@ public class FlatContainerTest
addInitialTabs( tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 ); addInitialTabs( tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 );
initialTabCount = tabbedPane1.getTabCount(); initialTabCount = tabbedPane1.getTabCount();
tabsClosableCheckBox.setSelected( true );
tabsClosableChanged();
putTabbedPanesClientProperty( TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT, "Close" );
tabScrollCheckBox.setSelected( true ); tabScrollCheckBox.setSelected( true );
tabScrollChanged(); tabScrollChanged();
} }
@@ -123,14 +130,15 @@ public class FlatContainerTest
private void addInitialTabs( JTabbedPane... tabbedPanes ) { private void addInitialTabs( JTabbedPane... tabbedPanes ) {
for( JTabbedPane tabbedPane : tabbedPanes ) { for( JTabbedPane tabbedPane : tabbedPanes ) {
tabbedPane.addTab( "Tab 1", new Panel1() ); tabbedPane.addTab( "Tab 1", null, new Panel1(), "First tab." );
JComponent tab2 = new Panel2(); JComponent tab2 = new Panel2();
tab2.setBorder( new LineBorder( Color.magenta ) ); tab2.setBorder( new LineBorder( Color.magenta ) );
tabbedPane.addTab( "Second Tab", tab2 ); tabbedPane.addTab( "Second Tab", null, tab2, "This is the second tab." );
addTab( tabbedPane, "Disabled", "tab content 3" ); addTab( tabbedPane, "Disabled", "tab content 3" );
tabbedPane.setEnabledAt( 2, false ); tabbedPane.setEnabledAt( 2, false );
tabbedPane.setToolTipTextAt( 2, "Disabled tab." );
tabbedPane.addTab( "Tab 4", new JLabel( "non-opaque content", SwingConstants.CENTER ) ); tabbedPane.addTab( "Tab 4", new JLabel( "non-opaque content", SwingConstants.CENTER ) );
} }
@@ -236,10 +244,7 @@ public class FlatContainerTest
case "arrowButtons": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS; break; case "arrowButtons": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS; break;
} }
tabbedPane1.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); putTabbedPanesClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value );
tabbedPane2.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value );
tabbedPane3.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value );
tabbedPane4.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value );
} }
private void tabBackForegroundChanged() { private void tabBackForegroundChanged() {
@@ -270,6 +275,52 @@ public class FlatContainerTest
} }
} }
private void tabsClosableChanged() {
boolean closable = tabsClosableCheckBox.isSelected();
putTabbedPanesClientProperty( TABBED_PANE_TAB_CLOSABLE, closable ? true : null );
if( closable ) {
putTabbedPanesClientProperty( TABBED_PANE_TAB_CLOSE_CALLBACK,
(BiConsumer<JTabbedPane, Integer>) (tabbedPane, tabIndex) -> {
AWTEvent e = EventQueue.getCurrentEvent();
int modifiers = (e instanceof MouseEvent) ? ((MouseEvent)e).getModifiers() : 0;
JOptionPane.showMessageDialog( this, "Closed tab '" + tabbedPane.getTitleAt( tabIndex ) + "'."
+ "\n\n(modifiers: " + MouseEvent.getMouseModifiersText( modifiers ) + ")",
"Tab Closed", JOptionPane.PLAIN_MESSAGE );
} );
}
}
private void secondTabClosableChanged() {
Boolean value = secondTabClosableCheckBox.getValue();
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 );
}
}
private void smallerTabHeightChanged() {
Integer tabHeight = smallerTabHeightCheckBox.isSelected() ? 26 : null;
putTabbedPanesClientProperty( TABBED_PANE_TAB_HEIGHT, tabHeight );
}
private void smallerInsetsChanged() {
Insets insets = smallerInsetsCheckBox.isSelected() ? new Insets( 2, 2, 2, 2 ) : null;
putTabbedPanesClientProperty( TABBED_PANE_TAB_INSETS, insets );
}
private void secondTabWiderChanged() {
Insets insets = secondTabWiderCheckBox.isSelected() ? new Insets( 4, 20, 4, 20 ) : null;
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 );
}
}
private void initComponents() { private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JPanel panel9 = new JPanel(); JPanel panel9 = new JPanel();
@@ -306,6 +357,11 @@ public class FlatContainerTest
tabBackForegroundCheckBox = new JCheckBox(); tabBackForegroundCheckBox = new JCheckBox();
leadingComponentCheckBox = new JCheckBox(); leadingComponentCheckBox = new JCheckBox();
trailingComponentCheckBox = new JCheckBox(); trailingComponentCheckBox = new JCheckBox();
tabsClosableCheckBox = new JCheckBox();
secondTabClosableCheckBox = new TriStateCheckBox();
smallerTabHeightCheckBox = new JCheckBox();
smallerInsetsCheckBox = new JCheckBox();
secondTabWiderCheckBox = new JCheckBox();
CellConstraints cc = new CellConstraints(); CellConstraints cc = new CellConstraints();
//======== this ======== //======== this ========
@@ -420,6 +476,7 @@ public class FlatContainerTest
"[center]" + "[center]" +
"[]" + "[]" +
"[]" + "[]" +
"[]" +
"[]")); "[]"));
//---- moreTabsCheckBox ---- //---- moreTabsCheckBox ----
@@ -518,6 +575,31 @@ public class FlatContainerTest
trailingComponentCheckBox.setText("Trailing"); trailingComponentCheckBox.setText("Trailing");
trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged()); trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged());
panel14.add(trailingComponentCheckBox, "cell 1 3"); panel14.add(trailingComponentCheckBox, "cell 1 3");
//---- tabsClosableCheckBox ----
tabsClosableCheckBox.setText("Tabs closable");
tabsClosableCheckBox.addActionListener(e -> tabsClosableChanged());
panel14.add(tabsClosableCheckBox, "cell 2 3");
//---- secondTabClosableCheckBox ----
secondTabClosableCheckBox.setText("Second Tab closable");
secondTabClosableCheckBox.addActionListener(e -> secondTabClosableChanged());
panel14.add(secondTabClosableCheckBox, "cell 3 3");
//---- smallerTabHeightCheckBox ----
smallerTabHeightCheckBox.setText("Smaller tab height");
smallerTabHeightCheckBox.addActionListener(e -> smallerTabHeightChanged());
panel14.add(smallerTabHeightCheckBox, "cell 0 4 2 1");
//---- smallerInsetsCheckBox ----
smallerInsetsCheckBox.setText("Smaller insets");
smallerInsetsCheckBox.addActionListener(e -> smallerInsetsChanged());
panel14.add(smallerInsetsCheckBox, "cell 2 4");
//---- secondTabWiderCheckBox ----
secondTabWiderCheckBox.setText("Second Tab wider");
secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged());
panel14.add(secondTabWiderCheckBox, "cell 3 4");
} }
panel9.add(panel14, cc.xywh(1, 11, 3, 1)); panel9.add(panel14, cc.xywh(1, 11, 3, 1));
} }
@@ -545,6 +627,11 @@ public class FlatContainerTest
private JCheckBox tabBackForegroundCheckBox; private JCheckBox tabBackForegroundCheckBox;
private JCheckBox leadingComponentCheckBox; private JCheckBox leadingComponentCheckBox;
private JCheckBox trailingComponentCheckBox; private JCheckBox trailingComponentCheckBox;
private JCheckBox tabsClosableCheckBox;
private TriStateCheckBox secondTabClosableCheckBox;
private JCheckBox smallerTabHeightCheckBox;
private JCheckBox smallerInsetsCheckBox;
private JCheckBox secondTabWiderCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
//---- class Tab1Panel ---------------------------------------------------- //---- class Tab1Panel ----------------------------------------------------

View File

@@ -132,7 +132,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3" "$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[][fill][][][fill]" "$columnConstraints": "[][fill][][][fill]"
"$rowConstraints": "[center][][][]" "$rowConstraints": "[center][][][][]"
} ) { } ) {
name: "panel14" name: "panel14"
"opaque": false "opaque": false
@@ -327,6 +327,56 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 3" "value": "cell 1 3"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "tabsClosableCheckBox"
"text": "Tabs closable"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabsClosableChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3"
} )
add( new FormComponent( "com.formdev.flatlaf.extras.TriStateCheckBox" ) {
name: "secondTabClosableCheckBox"
"text": "Second Tab closable"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabClosableChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 3"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "smallerTabHeightCheckBox"
"text": "Smaller tab height"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerTabHeightChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4 2 1"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "smallerInsetsCheckBox"
"text": "Smaller insets"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerInsetsChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 4"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "secondTabWiderCheckBox"
"text": "Second Tab wider"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabWiderChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 4"
} )
}, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11 "gridY": 11
"gridWidth": 3 "gridWidth": 3

View File

@@ -284,6 +284,18 @@ TabbedPane.focusColor=#ddd
TabbedPane.tabSeparatorColor=#00f TabbedPane.tabSeparatorColor=#00f
TabbedPane.contentAreaColor=#bbb TabbedPane.contentAreaColor=#bbb
TabbedPane.closeSize=16,16
TabbedPane.closeArc=999
TabbedPane.closeCrossPlainSize={float}12
TabbedPane.closeCrossFilledSize={float}6.5
TabbedPane.closeCrossLineWidth={float}2
#TabbedPane.closeBackground=#faa
TabbedPane.closeForeground=#f00
TabbedPane.closeHoverBackground=#a00
TabbedPane.closeHoverForeground=#fff
TabbedPane.closePressedBackground=#0f0
TabbedPane.closePressedForeground=#000
#---- Table ---- #---- Table ----

View File

@@ -634,6 +634,17 @@ SplitPaneDivider.oneTouchHoverArrowColor
SplitPaneUI SplitPaneUI
TabbedPane.ancestorInputMap TabbedPane.ancestorInputMap
TabbedPane.background TabbedPane.background
TabbedPane.closeArc
TabbedPane.closeCrossFilledSize
TabbedPane.closeCrossLineWidth
TabbedPane.closeCrossPlainSize
TabbedPane.closeForeground
TabbedPane.closeHoverBackground
TabbedPane.closeHoverForeground
TabbedPane.closeIcon
TabbedPane.closePressedBackground
TabbedPane.closePressedForeground
TabbedPane.closeSize
TabbedPane.contentAreaColor TabbedPane.contentAreaColor
TabbedPane.contentOpaque TabbedPane.contentOpaque
TabbedPane.contentSeparatorHeight TabbedPane.contentSeparatorHeight