TabbedPane: support closable tabs (issue #40)

This commit is contained in:
Karl Tauber
2020-10-20 09:37:28 +02:00
parent 8ccda81d9a
commit 700bb9b567
15 changed files with 583 additions and 37 deletions

View File

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

View File

@@ -25,6 +25,8 @@ import javax.swing.JComponent;
*/
public interface FlatClientProperties
{
//---- JButton ------------------------------------------------------------
/**
* Specifies type of a button.
* <p>
@@ -104,6 +106,8 @@ public interface FlatClientProperties
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JComponent ---------------------------------------------------------
/**
* Specifies minimum width of a component.
* <p>
@@ -158,6 +162,8 @@ public interface FlatClientProperties
*/
String COMPONENT_ROUND_RECT = "JComponent.roundRect";
//---- 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.
@@ -167,6 +173,8 @@ public interface FlatClientProperties
*/
String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted";
//---- JProgressBar -------------------------------------------------------
/**
* Specifies whether the progress bar has always the larger height even if no string is painted.
* <p>
@@ -183,6 +191,8 @@ public interface FlatClientProperties
*/
String PROGRESS_BAR_SQUARE = "JProgressBar.square";
//---- JRootPane ----------------------------------------------------------
/**
* Specifies whether the menu bar is embedded into the title pane if custom
* window decorations are enabled. Default is {@code true}.
@@ -192,6 +202,8 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
//---- JScrollBar ---------------------------------------------------------
/**
* Specifies whether the decrease/increase arrow buttons of a scrollbar are shown.
* <p>
@@ -208,6 +220,8 @@ public interface FlatClientProperties
*/
String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling";
//---- JTabbedPane --------------------------------------------------------
/**
* Specifies whether separators are shown between tabs.
* <p>
@@ -240,6 +254,60 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight";
/**
* 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 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.
* <p>
@@ -280,6 +348,8 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent";
//---- JTextField ---------------------------------------------------------
/**
* Specifies whether all text is selected when the text component gains focus.
* <p>
@@ -322,6 +392,8 @@ public interface FlatClientProperties
*/
String PLACEHOLDER_TEXT = "JTextField.placeholderText";
//---- JToggleButton ------------------------------------------------------
/**
* Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p>
@@ -346,6 +418,8 @@ public interface FlatClientProperties
*/
String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground";
//---- helper methods -----------------------------------------------------
/**
* 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.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
@@ -51,6 +54,10 @@ import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.Locale;
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.JComponent;
import javax.swing.JMenuItem;
@@ -119,6 +126,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault ScrollPane.smoothScrolling boolean
* @uiDefault TabbedPane.closeIcon Icon
*
* @uiDefault TabbedPane.moreTabsButtonToolTipText String
*
@@ -152,18 +160,22 @@ public class FlatTabbedPaneUI
protected boolean hasFullBorder;
private String hiddenTabsNavigationStr;
protected Icon closeIcon;
protected String moreTabsButtonToolTipText;
protected JViewport tabViewport;
protected FlatWheelTabScroller wheelTabScroller;
private JButton tabCloseButton;
private JButton moreTabsButton;
private Container leadingComponent;
private Container trailingComponent;
private Handler handler;
private boolean blockRollover;
private boolean rolloverTabClose;
private boolean pressedTabClose;
public static ComponentUI createUI( JComponent c ) {
return new FlatTabbedPaneUI();
@@ -210,6 +222,7 @@ public class FlatTabbedPaneUI
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
Locale l = tabPane.getLocale();
moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l );
@@ -255,6 +268,8 @@ public class FlatTabbedPaneUI
tabSeparatorColor = null;
contentAreaColor = null;
closeIcon = null;
MigLayoutVisualPadding.uninstall( tabPane );
}
@@ -262,6 +277,11 @@ public class FlatTabbedPaneUI
protected void installComponents() {
super.installComponents();
// create tab close button
tabCloseButton = new TabCloseButton();
tabCloseButton.setVisible( false );
tabPane.add( tabCloseButton );
// find scrollable tab viewport
tabViewport = null;
if( isScrollTabLayout() ) {
@@ -288,6 +308,11 @@ public class FlatTabbedPaneUI
super.uninstallComponents();
if( tabCloseButton != null ) {
tabPane.remove( tabCloseButton );
tabCloseButton = null;
}
tabViewport = null;
}
@@ -354,9 +379,7 @@ public class FlatTabbedPaneUI
protected void installListeners() {
super.installListeners();
tabPane.addMouseListener( getHandler() );
tabPane.addMouseMotionListener( getHandler() );
tabPane.addComponentListener( getHandler() );
getHandler().installListeners();
if( tabViewport != null && (wheelTabScroller = createWheelTabScroller()) != null ) {
// ideally we would add the mouse listeners to the viewport, but then the
@@ -373,9 +396,7 @@ public class FlatTabbedPaneUI
super.uninstallListeners();
if( handler != null ) {
tabPane.removeMouseListener( handler );
tabPane.removeMouseMotionListener( handler );
tabPane.removeComponentListener( handler );
handler.uninstallListeners();
handler = null;
}
@@ -399,6 +420,13 @@ public class FlatTabbedPaneUI
return new FlatWheelTabScroller();
}
@Override
protected MouseListener createMouseListener() {
Handler handler = getHandler();
handler.mouseDelegate = super.createMouseListener();
return handler;
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
Handler handler = getHandler();
@@ -454,6 +482,30 @@ public class FlatTabbedPaneUI
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 ) {
if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() )
return;
@@ -465,7 +517,10 @@ public class FlatTabbedPaneUI
@Override
protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) {
return super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
if( isTabClosable( tabIndex ) )
tabWidth += closeIcon.getIconWidth();
return tabWidth;
}
@Override
@@ -528,6 +583,10 @@ public class FlatTabbedPaneUI
@Override
protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) {
if( isTabClosable( tabIndex ) ) {
int shift = closeIcon.getIconWidth() / 2;
return isLeftToRight() ? -shift : shift;
}
return 0;
}
@@ -619,6 +678,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 ) )
@@ -628,6 +691,18 @@ public class FlatTabbedPaneUI
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 ) {
float sepWidth = UIScale.scale( 1f );
float offset = tabSeparatorsFullHeight ? 0 : UIScale.scale( 5f );
@@ -636,7 +711,7 @@ public class FlatTabbedPaneUI
if( tabPlacement == LEFT || tabPlacement == RIGHT ) {
// paint tab separator at bottom side
((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
((Graphics2D)g).fill( new Rectangle2D.Float( x + w - sepWidth, y + offset, sepWidth, h - (offset * 2) ) );
} else {
@@ -819,6 +894,55 @@ public class FlatTabbedPaneUI
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() {
// since super.ensureCurrentLayout() is private,
// use super.getTabRunCount() as workaround
@@ -834,6 +958,10 @@ public class FlatTabbedPaneUI
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
}
private boolean isLeftToRight() {
return tabPane.getComponentOrientation().isLeftToRight();
}
protected boolean isHorizontalTabPlacement() {
int tabPlacement = tabPane.getTabPlacement();
return tabPlacement == TOP || tabPlacement == BOTTOM;
@@ -914,6 +1042,16 @@ public class FlatTabbedPaneUI
}
}
//---- class TabCloseButton -----------------------------------------------
private class TabCloseButton
extends JButton
implements UIResource
{
private TabCloseButton() {
}
}
//---- class ContainerUIResource ------------------------------------------
private class ContainerUIResource
@@ -1011,9 +1149,8 @@ public class FlatTabbedPaneUI
int buttonWidth = getWidth();
int buttonHeight = getHeight();
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;
switch( tabPane.getTabPlacement() ) {
default:
@@ -1185,8 +1322,7 @@ public class FlatTabbedPaneUI
int y = viewPosition.y;
int tabPlacement = tabPane.getTabPlacement();
if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
x += leftToRight ? amount : -amount;
x += isLeftToRight() ? amount : -amount;
x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() );
} else {
y += amount;
@@ -1360,18 +1496,69 @@ public class FlatTabbedPaneUI
//---- class Handler ------------------------------------------------------
private class Handler
extends MouseAdapter
implements PropertyChangeListener, ChangeListener, ComponentListener
implements MouseListener, MouseMotionListener, PropertyChangeListener,
ChangeListener, ComponentListener, ContainerListener
{
MouseListener mouseDelegate;
PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate;
private int pressedTabIndex = -1;
void installListeners() {
tabPane.addMouseMotionListener( this );
tabPane.addComponentListener( this );
tabPane.addContainerListener( this );
for( Component c : tabPane.getComponents() ) {
if( !(c instanceof UIResource) )
c.addPropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this );
}
}
void uninstallListeners() {
tabPane.removeMouseMotionListener( this );
tabPane.removeComponentListener( this );
tabPane.removeContainerListener( this );
for( Component c : tabPane.getComponents() ) {
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this );
}
}
//---- interface MouseListener ----
@Override
public void mouseClicked( MouseEvent e ) {
mouseDelegate.mouseClicked( e );
}
@Override
public void mousePressed( MouseEvent e ) {
updateRollover( e, true );
if( !isPressedTabClose() )
mouseDelegate.mousePressed( e );
}
@Override
public void mouseReleased( MouseEvent e ) {
if( isPressedTabClose() ) {
updateRollover( e, false );
if( pressedTabIndex >= 0 && pressedTabIndex == getRolloverTab() )
closeTab( pressedTabIndex );
} else
mouseDelegate.mouseReleased( e );
pressedTabIndex = -1;
updateRollover( e, false );
}
@Override
public void mouseEntered( MouseEvent e ) {
// this is necessary for "more tabs" button
setRolloverTab( e.getX(), e.getY() );
updateRollover( e, false );
}
@Override
@@ -1379,15 +1566,37 @@ public class FlatTabbedPaneUI
// this event occurs also if mouse is moved to a custom tab component
// that handles mouse events (e.g. a close button)
// --> make sure that the tab stays highlighted
setRolloverTab( e.getX(), e.getY() );
updateRollover( e, false );
}
//---- interface MouseMotionListener ----
@Override
public void mouseDragged( MouseEvent e ) {
updateRollover( e, false );
}
@Override
public void mouseMoved( MouseEvent e ) {
// this is necessary for "more tabs" button
setRolloverTab( e.getX(), e.getY() );
updateRollover( e, false );
}
private void updateRollover( MouseEvent e, boolean pressed ) {
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( pressed )
pressedTabIndex = hitClose ? tabIndex : -1;
setRolloverTabClose( hitClose );
setPressedTabClose( hitClose && tabIndex == pressedTabIndex );
}
//---- interface PropertyChangeListener ----
@@ -1395,6 +1604,7 @@ public class FlatTabbedPaneUI
@Override
public void propertyChange( PropertyChangeEvent e ) {
// invoke delegate listener
if( e.getSource() instanceof JTabbedPane ) {
switch( e.getPropertyName() ) {
case "tabPlacement":
case "opaque":
@@ -1409,6 +1619,7 @@ public class FlatTabbedPaneUI
propertyChangeDelegate.propertyChange( e );
break;
}
}
// handle event
switch( e.getPropertyName() ) {
@@ -1426,6 +1637,7 @@ public class FlatTabbedPaneUI
case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_TAB_HEIGHT:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate();
tabPane.repaint();
break;
@@ -1470,6 +1682,22 @@ public class FlatTabbedPaneUI
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentShown( 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( TABBED_PANE_TAB_CLOSABLE, this );
}
@Override
public void componentRemoved( ContainerEvent e ) {
Component c = e.getChild();
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this );
}
}
//---- class FlatTabbedPaneLayout -----------------------------------------
@@ -1485,7 +1713,7 @@ public class FlatTabbedPaneUI
Insets insets = tabPane.getInsets();
int tabPlacement = tabPane.getTabPlacement();
Insets tabAreaInsets = getTabAreaInsets( tabPlacement );
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
boolean leftToRight = isLeftToRight();
// layout leading and trailing components in tab area
if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
@@ -1556,7 +1784,7 @@ public class FlatTabbedPaneUI
* Layout manager used for scroll tab layout policy.
* <p>
* 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
* also extends {@link TabbedPaneLayout} to avoid a {@code ClassCastException}
* in {@link BasicTabbedPaneUI}.ensureCurrentLayout().
@@ -1624,7 +1852,7 @@ public class FlatTabbedPaneUI
Dimension moreButtonSize = useMoreButton ? moreTabsButton.getPreferredSize() : null;
Dimension backwardButtonSize = useMoreButton ? null : backwardButton.getPreferredSize();
Dimension forwardButtonSize = useMoreButton ? null : forwardButton.getPreferredSize();
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
boolean leftToRight = isLeftToRight();
boolean buttonsVisible = false;
// TabbedPaneScrollLayout adds tabAreaInsets to tab coordinates,

View File

@@ -258,6 +258,13 @@ TabbedPane.hoverColor=#2e3133
TabbedPane.focusColor=#3d4b5c
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 ----

View File

@@ -552,6 +552,13 @@ TabbedPane.shadow=@background
TabbedPane.contentBorderInsets=null
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 ----

View File

@@ -270,6 +270,13 @@ TabbedPane.hoverColor=#d9d9d9
TabbedPane.focusColor=#dae4ed
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 ----

View File

@@ -95,6 +95,19 @@ public class TriStateCheckBox
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() {
return thirdStateEnabled;
}

View File

@@ -905,6 +905,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ----
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.contentOpaque true
TabbedPane.contentSeparatorHeight 1

View File

@@ -910,6 +910,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ----
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.contentOpaque true
TabbedPane.contentSeparatorHeight 1

View File

@@ -898,6 +898,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI
#---- TabbedPane ----
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.contentOpaque true
TabbedPane.contentSeparatorHeight 1

View File

@@ -18,9 +18,12 @@ package com.formdev.flatlaf.testing;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.function.BiConsumer;
import javax.swing.*;
import javax.swing.border.*;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.extras.TriStateCheckBox;
import com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.jgoodies.forms.layout.*;
@@ -49,6 +52,9 @@ public class FlatContainerTest
addInitialTabs( tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 );
initialTabCount = tabbedPane1.getTabCount();
tabsClosableCheckBox.setSelected( true );
tabsClosableChanged();
tabScrollCheckBox.setSelected( true );
tabScrollChanged();
}
@@ -236,10 +242,7 @@ public class FlatContainerTest
case "arrowButtons": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS; break;
}
tabbedPane1.putClientProperty( 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 );
putTabbedPanesClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value );
}
private void tabBackForegroundChanged() {
@@ -270,6 +273,32 @@ 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 initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JPanel panel9 = new JPanel();
@@ -306,6 +335,8 @@ public class FlatContainerTest
tabBackForegroundCheckBox = new JCheckBox();
leadingComponentCheckBox = new JCheckBox();
trailingComponentCheckBox = new JCheckBox();
tabsClosableCheckBox = new JCheckBox();
secondTabClosableCheckBox = new TriStateCheckBox();
CellConstraints cc = new CellConstraints();
//======== this ========
@@ -518,6 +549,16 @@ public class FlatContainerTest
trailingComponentCheckBox.setText("Trailing");
trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged());
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");
}
panel9.add(panel14, cc.xywh(1, 11, 3, 1));
}
@@ -545,6 +586,8 @@ public class FlatContainerTest
private JCheckBox tabBackForegroundCheckBox;
private JCheckBox leadingComponentCheckBox;
private JCheckBox trailingComponentCheckBox;
private JCheckBox tabsClosableCheckBox;
private TriStateCheckBox secondTabClosableCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables
//---- class Tab1Panel ----------------------------------------------------

View File

@@ -327,6 +327,26 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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"
} )
}, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11
"gridWidth": 3

View File

@@ -284,6 +284,18 @@ TabbedPane.focusColor=#ddd
TabbedPane.tabSeparatorColor=#00f
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 ----

View File

@@ -634,6 +634,17 @@ SplitPaneDivider.oneTouchHoverArrowColor
SplitPaneUI
TabbedPane.ancestorInputMap
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.contentOpaque
TabbedPane.contentSeparatorHeight