TabbedPane: replaced forward/backward scrolling arrow buttons with "Show Hidden Tabs" button (issue #40)

This commit is contained in:
Karl Tauber
2020-10-15 00:10:07 +02:00
parent ae445c9343
commit c58f5a6ca7
11 changed files with 596 additions and 19 deletions

View File

@@ -5,6 +5,11 @@ FlatLaf Change Log
#### New features and improvements #### New features and improvements
- TabbedPane: Replaced forward/backward scrolling arrow buttons with "Show
Hidden Tabs" button. If pressed, it shows a popup menu that contains (partly)
hidden tabs and selecting one activates that tab. If you prefer
forward/backward buttons, use `UIManager.put(
"TabbedPane.hiddenTabsNavigation", "arrowButtons" )`. (issue #40)
- TabbedPane: Support scrolling tabs with mouse wheel (if `tabLayoutPolicy` is - TabbedPane: Support scrolling tabs with mouse wheel (if `tabLayoutPolicy` is
`SCROLL_TAB_LAYOUT`). (issue #40) `SCROLL_TAB_LAYOUT`). (issue #40)
- TabbedPane: Repeat scrolling as long as arrow buttons are pressed. (issue #40) - TabbedPane: Repeat scrolling as long as arrow buttons are pressed. (issue #40)

View File

@@ -20,16 +20,23 @@ import static com.formdev.flatlaf.util.UIScale.scale;
import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
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;
@@ -41,15 +48,22 @@ import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Collections; import java.util.Collections;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.Timer; import javax.swing.Timer;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.plaf.basic.BasicTabbedPaneUI;
@@ -100,6 +114,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.showTabSeparators boolean * @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.tabSeparatorsFullHeight boolean * @uiDefault TabbedPane.tabSeparatorsFullHeight boolean
* @uiDefault TabbedPane.hasFullBorder boolean * @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault ScrollPane.smoothScrolling boolean * @uiDefault ScrollPane.smoothScrolling boolean
* *
* @author Karl Tauber * @author Karl Tauber
@@ -107,6 +122,10 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTabbedPaneUI public class FlatTabbedPaneUI
extends BasicTabbedPaneUI extends BasicTabbedPaneUI
{ {
// hidden tabs navigation types
protected static final int MORE_TABS_BUTTON = 0;
protected static final int ARROW_BUTTONS = 1;
private static Set<KeyStroke> focusForwardTraversalKeys; private static Set<KeyStroke> focusForwardTraversalKeys;
private static Set<KeyStroke> focusBackwardTraversalKeys; private static Set<KeyStroke> focusBackwardTraversalKeys;
@@ -128,9 +147,15 @@ public class FlatTabbedPaneUI
protected boolean hasFullBorder; protected boolean hasFullBorder;
protected boolean tabsOverlapBorder; protected boolean tabsOverlapBorder;
protected int hiddenTabsNavigation = MORE_TABS_BUTTON;
protected String moreTabsButtonToolTipText;
protected JViewport tabViewport; protected JViewport tabViewport;
protected FlatWheelTabScroller wheelTabScroller; protected FlatWheelTabScroller wheelTabScroller;
private JButton moreTabsButton;
private Handler handler; private Handler handler;
private boolean blockRollover; private boolean blockRollover;
@@ -140,7 +165,28 @@ public class FlatTabbedPaneUI
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); if( UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" ) ) {
// Force BasicTabbedPaneUI.tabsOverlapBorder to false,
// which is necessary for "more tabs" button to work correctly.
//
// If it would be true, class TabbedPaneScrollLayout would invoke TabbedPaneLayout.padSelectedTab(),
// which would modify rectangle of selected tab in a wrong way (for wrap tab layout policy).
// This would cause tab painting issues when scrolled and
// missing "more tabs" button if last tab is selected.
//
// All methods of BasicTabbedPaneUI that use tabsOverlapBorder (except
// the one method mentioned above) are overridden.
//
// This is normally not invoked because the default value for
// TabbedPane.tabsOverlapBorder is false in all FlatLaf themes.
// Anyway, 3rd party themes may have changed it.
// So make sure that it works anyway to avoid issues.
Object oldValue = UIManager.put( "TabbedPane.tabsOverlapBorder", false );
super.installDefaults();
UIManager.put( "TabbedPane.tabsOverlapBorder", oldValue );
} else
super.installDefaults();
disabledForeground = UIManager.getColor( "TabbedPane.disabledForeground" ); disabledForeground = UIManager.getColor( "TabbedPane.disabledForeground" );
selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" ); selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" );
@@ -160,6 +206,9 @@ public class FlatTabbedPaneUI
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" ); tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" );
Locale l = tabPane.getLocale();
moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l );
// scale // scale
textIconGap = scale( textIconGap ); textIconGap = scale( textIconGap );
tabInsets = scale( tabInsets ); tabInsets = scale( tabInsets );
@@ -218,6 +267,47 @@ public class FlatTabbedPaneUI
} }
} }
} }
// initialize here because used in installHiddenTabsNavigation() before installDefaults() was invoked
hiddenTabsNavigation = parseHiddenTabsNavigation( UIManager.getString( "TabbedPane.hiddenTabsNavigation" ) );
installHiddenTabsNavigation();
}
private void installHiddenTabsNavigation() {
if( hiddenTabsNavigation != MORE_TABS_BUTTON ||
!isScrollTabLayout() ||
tabViewport == null )
return;
// At this point, BasicTabbedPaneUI already has installed
// TabbedPaneScrollLayout (in super.createLayoutManager()) and
// ScrollableTabSupport, ScrollableTabViewport, ScrollableTabPanel, etc
// (in super.installComponents()).
// install own layout manager that delegates to original layout manager
tabPane.setLayout( createScrollLayoutManager( (TabbedPaneLayout) tabPane.getLayout() ) );
// create and add "more tabs" button
moreTabsButton = createMoreTabsButton();
tabPane.add( moreTabsButton );
}
@Override
protected void uninstallComponents() {
// restore layout manager before invoking super.uninstallComponents() for
// correct uninstallation of BasicTabbedPaneUI tab scroller support
if( tabPane.getLayout() instanceof FlatTabbedPaneScrollLayout )
tabPane.setLayout( ((FlatTabbedPaneScrollLayout)tabPane.getLayout()).delegate );
super.uninstallComponents();
if( moreTabsButton != null ) {
tabPane.remove( moreTabsButton );
moreTabsButton = null;
}
tabViewport = null;
} }
@Override @Override
@@ -225,6 +315,8 @@ public class FlatTabbedPaneUI
super.installListeners(); super.installListeners();
tabPane.addMouseListener( getHandler() ); tabPane.addMouseListener( getHandler() );
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
@@ -242,6 +334,8 @@ public class FlatTabbedPaneUI
if( handler != null ) { if( handler != null ) {
tabPane.removeMouseListener( handler ); tabPane.removeMouseListener( handler );
tabPane.removeMouseMotionListener( handler );
tabPane.removeComponentListener( handler );
handler = null; handler = null;
} }
@@ -272,6 +366,21 @@ public class FlatTabbedPaneUI
return handler; return handler;
} }
@Override
protected ChangeListener createChangeListener() {
Handler handler = getHandler();
handler.changeDelegate = super.createChangeListener();
return handler;
}
protected LayoutManager createScrollLayoutManager( TabbedPaneLayout delegate ) {
return new FlatTabbedPaneScrollLayout( delegate );
}
protected JButton createMoreTabsButton() {
return new FlatMoreTabsButton();
}
@Override @Override
protected JButton createScrollButton( int direction ) { protected JButton createScrollButton( int direction ) {
return new FlatScrollableTabButton( direction ); return new FlatScrollableTabButton( direction );
@@ -353,6 +462,19 @@ public class FlatTabbedPaneUI
super.update( g, c ); super.update( g, c );
} }
@Override
public void paint( Graphics g, JComponent c ) {
ensureCurrentLayout();
int tabPlacement = tabPane.getTabPlacement();
int selectedIndex = tabPane.getSelectedIndex();
paintContentBorder( g, tabPlacement, selectedIndex );
if( !isScrollTabLayout() )
paintTabArea( g, tabPlacement, selectedIndex );
}
@Override @Override
protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics, protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics,
int tabIndex, String title, Rectangle textRect, boolean isSelected ) int tabIndex, String title, Rectangle textRect, boolean isSelected )
@@ -573,6 +695,43 @@ public class FlatTabbedPaneUI
{ {
} }
@Override
public int tabForCoordinate( JTabbedPane pane, int x, int y ) {
if( moreTabsButton != null ) {
// convert x,y from JTabbedPane coordinate space to ScrollableTabPanel coordinate space
Point viewPosition = tabViewport.getViewPosition();
x = x - tabViewport.getX() + viewPosition.x;
y = y - tabViewport.getY() + viewPosition.y;
// check whether point is within viewport
if( !tabViewport.getViewRect().contains( x, y ) )
return -1;
}
return super.tabForCoordinate( pane, x, y );
}
@Override
protected Rectangle getTabBounds( int tabIndex, Rectangle dest ) {
if( moreTabsButton != null ) {
// copy tab bounds to dest
dest.setBounds( rects[tabIndex] );
// convert tab bounds to coordinate space of JTabbedPane
Point viewPosition = tabViewport.getViewPosition();
dest.x = dest.x + tabViewport.getX() - viewPosition.x;
dest.y = dest.y + tabViewport.getY() - viewPosition.y;
return dest;
} else
return super.getTabBounds( tabIndex, dest );
}
protected void ensureCurrentLayout() {
// since super.ensureCurrentLayout() is private,
// use super.getTabRunCount() as workaround
super.getTabRunCount( tabPane );
}
private boolean isLastInRun( int tabIndex ) { private boolean isLastInRun( int tabIndex ) {
int run = getRunForTab( tabPane.getTabCount(), tabIndex ); int run = getRunForTab( tabPane.getTabCount(), tabIndex );
return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex; return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex;
@@ -592,6 +751,170 @@ public class FlatTabbedPaneUI
return UIManager.getBoolean( "ScrollPane.smoothScrolling" ); return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
} }
protected static int parseHiddenTabsNavigation( String str ) {
if( str == null )
return MORE_TABS_BUTTON;
switch( str ) {
default:
case "moreTabsButton": return MORE_TABS_BUTTON;
case "arrowButtons": return ARROW_BUTTONS;
}
}
private void runWithOriginalLayoutManager( Runnable runnable ) {
LayoutManager layout = tabPane.getLayout();
if( layout instanceof FlatTabbedPaneScrollLayout ) {
// temporary change layout manager because the runnable may use
// BasicTabbedPaneUI.scrollableTabLayoutEnabled()
tabPane.setLayout( ((FlatTabbedPaneScrollLayout)layout).delegate );
runnable.run();
tabPane.setLayout( layout );
} else
runnable.run();
}
protected void ensureSelectedTabIsVisible() {
if( tabPane == null || tabViewport == null )
return;
int selectedIndex = tabPane.getSelectedIndex();
if( selectedIndex < 0 )
return;
Rectangle tabBounds = getTabBounds( tabPane, selectedIndex );
tabViewport.scrollRectToVisible( tabBounds );
}
//---- class FlatMoreTabsButton -------------------------------------------
protected class FlatMoreTabsButton
extends FlatArrowButton
implements ActionListener, PopupMenuListener
{
private boolean popupVisible;
public FlatMoreTabsButton() {
// this method is invoked before installDefaults(), so we can not use color fields here
super( SOUTH, UIManager.getString( "Component.arrowType" ),
UIManager.getColor( "TabbedPane.foreground" ),
UIManager.getColor( "TabbedPane.disabledForeground" ), null,
UIManager.getColor( "TabbedPane.hoverColor" ) );
updateDirection();
setToolTipText( moreTabsButtonToolTipText );
addActionListener( this );
}
protected void updateDirection() {
int direction;
switch( tabPane.getTabPlacement() ) {
default:
case TOP: direction = SOUTH; break;
case BOTTOM: direction = NORTH; break;
case LEFT: direction = EAST; break;
case RIGHT: direction = WEST; break;
}
setDirection( direction );
}
@Override
public void paint( Graphics g ) {
// paint arrow button near separator line
if( direction == EAST || direction == WEST ) {
int xoffset = (getWidth() / 2) - getHeight();
setXOffset( (direction == EAST) ? xoffset : -xoffset );
}
super.paint( g );
}
@Override
protected boolean isHover() {
return super.isHover() || popupVisible;
}
@Override
public void actionPerformed( ActionEvent e ) {
if( tabViewport == null )
return;
// detect (partly) hidden tabs and build popup menu
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.addPopupMenuListener( this );
Rectangle viewRect = tabViewport.getViewRect();
int lastIndex = -1;
for( int i = 0; i < rects.length; i++ ) {
if( !viewRect.contains( rects[i] ) ) {
// add separator between leading and trailing tabs
if( lastIndex >= 0 && lastIndex + 1 != i )
popupMenu.addSeparator();
lastIndex = i;
// create menu item for tab
popupMenu.add( createMenuItem( i ) );
}
}
// show popup menu
int buttonWidth = getWidth();
int buttonHeight = getHeight();
int x = 0;
int y = 0;
Dimension popupSize = popupMenu.getPreferredSize();
switch( tabPane.getTabPlacement() ) {
default:
case TOP: x = buttonWidth - popupSize.width; y = buttonHeight; break;
case BOTTOM: x = buttonWidth - popupSize.width; y = -popupSize.height; break;
case LEFT: x = buttonWidth; y = buttonHeight - popupSize.height; break;
case RIGHT: x = -popupSize.width; y = buttonHeight - popupSize.height; break;
}
popupMenu.show( this, x, y );
}
protected JMenuItem createMenuItem( int index ) {
JMenuItem menuItem = new JMenuItem( tabPane.getTitleAt( index ), tabPane.getIconAt( index ) );
menuItem.setDisabledIcon( tabPane.getDisabledIconAt( index ) );
menuItem.setToolTipText( tabPane.getToolTipTextAt( index ) );
Color foregroundAt = tabPane.getForegroundAt( index );
if( foregroundAt != tabPane.getForeground() )
menuItem.setForeground( foregroundAt );
Color backgroundAt = tabPane.getBackgroundAt( index );
if( backgroundAt != tabPane.getBackground() ) {
menuItem.setBackground( backgroundAt );
menuItem.setOpaque( true );
}
if( !tabPane.isEnabledAt( index ) )
menuItem.setEnabled( false );
menuItem.addActionListener( e -> tabPane.setSelectedIndex( index ) );
return menuItem;
}
@Override
public void popupMenuWillBecomeVisible( PopupMenuEvent e ) {
popupVisible = true;
repaint();
}
@Override
public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) {
popupVisible = false;
repaint();
}
@Override
public void popupMenuCanceled( PopupMenuEvent e ) {
popupVisible = false;
repaint();
}
}
//---- class FlatScrollableTabButton -------------------------------------- //---- class FlatScrollableTabButton --------------------------------------
protected class FlatScrollableTabButton protected class FlatScrollableTabButton
@@ -612,11 +935,21 @@ public class FlatTabbedPaneUI
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
// Use half width/height if "more tabs" button is used, because size of
// "more tabs" button is the union of the backward and forward scroll buttons.
// With this "trick", viewport gets correct size.
boolean halfSize = (hiddenTabsNavigation == MORE_TABS_BUTTON);
Dimension size = super.getPreferredSize(); Dimension size = super.getPreferredSize();
if( direction == WEST || direction == EAST ) if( direction == WEST || direction == EAST ) {
return new Dimension( size.width, Math.max( size.height, maxTabHeight ) ); return new Dimension(
else halfSize ? ((size.width / 2) + scale( 4 )) : size.width,
return new Dimension( Math.max( size.width, maxTabWidth ), size.height ); Math.max( size.height, maxTabHeight ) );
} else {
return new Dimension(
Math.max( size.width, maxTabWidth ),
halfSize ? ((size.height / 2) + scale( 4 )) : size.height );
}
} }
@Override @Override
@@ -871,11 +1204,8 @@ public class FlatTabbedPaneUI
return; return;
scrolled = false; scrolled = false;
int selectedIndex = tabPane.getSelectedIndex(); // scroll selected tab into visible area
if( selectedIndex >= 0 ) { ensureSelectedTabIsVisible();
Rectangle tabBounds = getTabBounds( tabPane, selectedIndex );
tabViewport.scrollRectToVisible( tabBounds );
}
} }
} }
@@ -883,9 +1213,18 @@ public class FlatTabbedPaneUI
private class Handler private class Handler
extends MouseAdapter extends MouseAdapter
implements PropertyChangeListener implements PropertyChangeListener, ChangeListener, ComponentListener
{ {
PropertyChangeListener propertyChangeDelegate; PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate;
//---- interface MouseListener ----
@Override
public void mouseEntered( MouseEvent e ) {
// this is necessary for "more tabs" button
setRolloverTab( e.getX(), e.getY() );
}
@Override @Override
public void mouseExited( MouseEvent e ) { public void mouseExited( MouseEvent e ) {
@@ -895,11 +1234,41 @@ public class FlatTabbedPaneUI
setRolloverTab( e.getX(), e.getY() ); setRolloverTab( e.getX(), e.getY() );
} }
//---- interface MouseMotionListener ----
@Override
public void mouseMoved( MouseEvent e ) {
// this is necessary for "more tabs" button
setRolloverTab( e.getX(), e.getY() );
}
//---- interface PropertyChangeListener ----
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
propertyChangeDelegate.propertyChange( e ); // invoke delegate listener
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case "tabPlacement":
case "opaque":
case "background":
case "indexForTabComponent":
runWithOriginalLayoutManager( () -> {
propertyChangeDelegate.propertyChange( e );
} );
break;
default:
propertyChangeDelegate.propertyChange( e );
break;
}
// handle event
switch( e.getPropertyName() ) {
case "tabPlacement":
if( moreTabsButton instanceof FlatMoreTabsButton )
((FlatMoreTabsButton)moreTabsButton).updateDirection();
break;
case TABBED_PANE_SHOW_TAB_SEPARATORS: case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER: case TABBED_PANE_HAS_FULL_BORDER:
@@ -909,5 +1278,112 @@ public class FlatTabbedPaneUI
break; break;
} }
} }
//---- interface ChangeListener ----
@Override
public void stateChanged( ChangeEvent e ) {
changeDelegate.stateChanged( e );
// scroll selected tab into visible area
if( moreTabsButton != null )
ensureSelectedTabIsVisible();
}
//---- interface ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
// make sure that selected tab stays visible when component size changed
EventQueue.invokeLater( () -> {
ensureSelectedTabIsVisible();
} );
}
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
//---- class FlatTabbedPaneScrollLayout -----------------------------------
/**
* Layout manager used if "TabbedPane.hiddenTabsNavigation" is "moreTabsButton".
* <p>
* Although this class delegates all methods to the original layout manager
* {@link 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().
*/
protected class FlatTabbedPaneScrollLayout
extends TabbedPaneLayout
implements LayoutManager
{
private final TabbedPaneLayout delegate;
protected FlatTabbedPaneScrollLayout( TabbedPaneLayout delegate ) {
this.delegate = delegate;
}
@Override
public void calculateLayoutInfo() {
delegate.calculateLayoutInfo();
}
//---- interface LayoutManager ----
@Override
public void addLayoutComponent( String name, Component comp ) {
delegate.addLayoutComponent( name, comp );
}
@Override
public void removeLayoutComponent( Component comp ) {
delegate.removeLayoutComponent( comp );
}
@Override
public Dimension preferredLayoutSize( Container parent ) {
return delegate.preferredLayoutSize( parent );
}
@Override
public Dimension minimumLayoutSize( Container parent ) {
return delegate.minimumLayoutSize( parent );
}
@Override
public void layoutContainer( Container parent ) {
// delegate to original layout manager and let it layout tabs and buttons
//
// runWithOriginalLayoutManager() is necessary for correct locations
// of tab components layed out in TabbedPaneLayout.layoutTabComponents()
runWithOriginalLayoutManager( () -> {
delegate.layoutContainer( parent );
} );
// check whether scroll buttons are visible, which is changed by original
// layout manager depending on whether there is enough room for all tabs
boolean scrollButtonsVisible = false;
Rectangle buttonsBounds = null;
for( Component c : tabPane.getComponents() ) {
if( c instanceof FlatScrollableTabButton && c.isVisible() ) {
scrollButtonsVisible = true;
// compute union bounds of all scroll buttons
Rectangle r = c.getBounds();
buttonsBounds = (buttonsBounds != null) ? buttonsBounds.union( r ) : r;
// hide scroll button
c.setVisible( false );
}
}
// show/hide "more tabs" button and layout it
moreTabsButton.setVisible( scrollButtonsVisible );
if( buttonsBounds != null )
moreTabsButton.setBounds( buttonsBounds );
}
} }
} }

View File

@@ -546,10 +546,11 @@ TabbedPane.tabInsets=4,12,4,12
TabbedPane.tabAreaInsets=0,0,0,0 TabbedPane.tabAreaInsets=0,0,0,0
TabbedPane.selectedTabPadInsets=0,0,0,0 TabbedPane.selectedTabPadInsets=0,0,0,0
TabbedPane.tabRunOverlay=0 TabbedPane.tabRunOverlay=0
TabbedPane.tabsOverlapBorder=true TabbedPane.tabsOverlapBorder=false
TabbedPane.disabledForeground=@disabledText TabbedPane.disabledForeground=@disabledText
TabbedPane.shadow=@background TabbedPane.shadow=@background
TabbedPane.contentBorderInsets=null TabbedPane.contentBorderInsets=null
TabbedPane.hiddenTabsNavigation=moreTabsButton
#---- Table ---- #---- Table ----

View File

@@ -46,3 +46,8 @@ FileChooser.refreshActionLabelText=Refresh
FileChooser.newFolderActionLabelText=New Folder FileChooser.newFolderActionLabelText=New Folder
FileChooser.listViewActionLabelText=List FileChooser.listViewActionLabelText=List
FileChooser.detailsViewActionLabelText=Details FileChooser.detailsViewActionLabelText=Details
#---- TabbedPane ----
TabbedPane.moreTabsButtonToolTipText=Show Hidden Tabs

View File

@@ -46,3 +46,8 @@ FileChooser.refreshActionLabelText=Aktualisieren
FileChooser.newFolderActionLabelText=Neuer Ordner FileChooser.newFolderActionLabelText=Neuer Ordner
FileChooser.listViewActionLabelText=Liste FileChooser.listViewActionLabelText=Liste
FileChooser.detailsViewActionLabelText=Details FileChooser.detailsViewActionLabelText=Details
#---- TabbedPane ----
TabbedPane.moreTabsButtonToolTipText=Verdeckte Tabs anzeigen

View File

@@ -916,6 +916,7 @@ TabbedPane.focusColor #3d4b5c javax.swing.plaf.ColorUIResource [UI]
TabbedPane.font [active] $defaultFont [UI] TabbedPane.font [active] $defaultFont [UI]
TabbedPane.foreground #bbbbbb javax.swing.plaf.ColorUIResource [UI] TabbedPane.foreground #bbbbbb javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hasFullBorder false TabbedPane.hasFullBorder false
TabbedPane.hiddenTabsNavigation moreTabsButton
TabbedPane.highlight #242424 javax.swing.plaf.ColorUIResource [UI] TabbedPane.highlight #242424 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hoverColor #2e3133 javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #2e3133 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.labelShift 1 TabbedPane.labelShift 1
@@ -932,7 +933,7 @@ TabbedPane.tabRunOverlay 0
TabbedPane.tabSelectionHeight 3 TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabSeparatorsFullHeight false
TabbedPane.tabsOpaque true TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder true TabbedPane.tabsOverlapBorder false
TabbedPane.textIconGap 4 TabbedPane.textIconGap 4
TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI]
TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI

View File

@@ -921,6 +921,7 @@ TabbedPane.focusColor #dae4ed javax.swing.plaf.ColorUIResource [UI]
TabbedPane.font [active] $defaultFont [UI] TabbedPane.font [active] $defaultFont [UI]
TabbedPane.foreground #000000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.foreground #000000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hasFullBorder false TabbedPane.hasFullBorder false
TabbedPane.hiddenTabsNavigation moreTabsButton
TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI] TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hoverColor #d9d9d9 javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #d9d9d9 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.labelShift 1 TabbedPane.labelShift 1
@@ -937,7 +938,7 @@ TabbedPane.tabRunOverlay 0
TabbedPane.tabSelectionHeight 3 TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabSeparatorsFullHeight false
TabbedPane.tabsOpaque true TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder true TabbedPane.tabsOverlapBorder false
TabbedPane.textIconGap 4 TabbedPane.textIconGap 4
TabbedPane.underlineColor #4083c9 javax.swing.plaf.ColorUIResource [UI] TabbedPane.underlineColor #4083c9 javax.swing.plaf.ColorUIResource [UI]
TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI

View File

@@ -909,6 +909,7 @@ TabbedPane.focusColor #dddddd javax.swing.plaf.ColorUIResource [UI]
TabbedPane.font [active] $defaultFont [UI] TabbedPane.font [active] $defaultFont [UI]
TabbedPane.foreground #ff0000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.foreground #ff0000 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hasFullBorder false TabbedPane.hasFullBorder false
TabbedPane.hiddenTabsNavigation moreTabsButton
TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI] TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI]
TabbedPane.hoverColor #eeeeee javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #eeeeee javax.swing.plaf.ColorUIResource [UI]
TabbedPane.labelShift 1 TabbedPane.labelShift 1
@@ -928,7 +929,7 @@ TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorColor #0000ff javax.swing.plaf.ColorUIResource [UI] TabbedPane.tabSeparatorColor #0000ff javax.swing.plaf.ColorUIResource [UI]
TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabSeparatorsFullHeight false
TabbedPane.tabsOpaque true TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder true TabbedPane.tabsOverlapBorder false
TabbedPane.textIconGap 4 TabbedPane.textIconGap 4
TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI]
TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI

View File

@@ -215,6 +215,27 @@ public class FlatContainerTest
return tab; return tab;
} }
private void tabPlacementChanged() {
int tabPlacement = -1;
switch( (String) tabPlacementField.getSelectedItem() ) {
case "top": tabPlacement = SwingConstants.TOP; break;
case "bottom": tabPlacement = SwingConstants.BOTTOM; break;
case "left": tabPlacement = SwingConstants.LEFT; break;
case "right": tabPlacement = SwingConstants.RIGHT; break;
}
tabbedPane1.setTabPlacement( (tabPlacement >= 0) ? tabPlacement : SwingConstants.TOP );
tabbedPane2.setTabPlacement( (tabPlacement >= 0) ? tabPlacement : SwingConstants.BOTTOM );
tabbedPane3.setTabPlacement( (tabPlacement >= 0) ? tabPlacement : SwingConstants.LEFT );
tabbedPane4.setTabPlacement( (tabPlacement >= 0) ? tabPlacement : SwingConstants.RIGHT );
}
private void tabBackForegroundChanged() {
boolean enabled = tabBackForegroundCheckBox.isSelected();
tabbedPane1.setBackgroundAt( 0, enabled ? Color.red : null );
tabbedPane1.setForegroundAt( 1, enabled ? Color.red : null );
}
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();
@@ -244,6 +265,9 @@ public class FlatContainerTest
customBorderCheckBox = new JCheckBox(); customBorderCheckBox = new JCheckBox();
customTabsCheckBox = new JCheckBox(); customTabsCheckBox = new JCheckBox();
hasFullBorderCheckBox = new JCheckBox(); hasFullBorderCheckBox = new JCheckBox();
JLabel tabPlacementLabel = new JLabel();
tabPlacementField = new JComboBox<>();
tabBackForegroundCheckBox = new JCheckBox();
CellConstraints cc = new CellConstraints(); CellConstraints cc = new CellConstraints();
//======== this ======== //======== this ========
@@ -356,6 +380,7 @@ public class FlatContainerTest
"[fill]", "[fill]",
// rows // rows
"[center]" + "[center]" +
"[]" +
"[]")); "[]"));
//---- moreTabsCheckBox ---- //---- moreTabsCheckBox ----
@@ -411,6 +436,26 @@ public class FlatContainerTest
hasFullBorderCheckBox.setText("Show content border"); hasFullBorderCheckBox.setText("Show content border");
hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged()); hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged());
panel14.add(hasFullBorderCheckBox, "cell 4 1,alignx left,growx 0"); panel14.add(hasFullBorderCheckBox, "cell 4 1,alignx left,growx 0");
//---- tabPlacementLabel ----
tabPlacementLabel.setText("Tab placement:");
panel14.add(tabPlacementLabel, "cell 0 2");
//---- tabPlacementField ----
tabPlacementField.setModel(new DefaultComboBoxModel<>(new String[] {
"default",
"top",
"bottom",
"left",
"right"
}));
tabPlacementField.addActionListener(e -> tabPlacementChanged());
panel14.add(tabPlacementField, "cell 1 2");
//---- tabBackForegroundCheckBox ----
tabBackForegroundCheckBox.setText("Tab back/foreground");
tabBackForegroundCheckBox.addActionListener(e -> tabBackForegroundChanged());
panel14.add(tabBackForegroundCheckBox, "cell 4 2");
} }
panel9.add(panel14, cc.xywh(1, 11, 3, 1)); panel9.add(panel14, cc.xywh(1, 11, 3, 1));
} }
@@ -433,6 +478,8 @@ public class FlatContainerTest
private JCheckBox customBorderCheckBox; private JCheckBox customBorderCheckBox;
private JCheckBox customTabsCheckBox; private JCheckBox customTabsCheckBox;
private JCheckBox hasFullBorderCheckBox; private JCheckBox hasFullBorderCheckBox;
private JComboBox<String> tabPlacementField;
private JCheckBox tabBackForegroundCheckBox;
// 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
@@ -251,6 +251,40 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 1,alignx left,growx 0" "value": "cell 4 1,alignx left,growx 0"
} ) } )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tabPlacementLabel"
"text": "Tab placement:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "tabPlacementField"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "default"
addElement( "default" )
addElement( "top" )
addElement( "bottom" )
addElement( "left" )
addElement( "right" )
}
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.typeParameters": "String"
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabPlacementChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 2"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "tabBackForegroundCheckBox"
"text": "Tab back/foreground"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabBackForegroundChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 2"
} )
}, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11 "gridY": 11
"gridWidth": 3 "gridWidth": 3
@@ -260,7 +294,7 @@ new FormModel {
} ) } )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 810, 515 ) "size": new java.awt.Dimension( 810, 570 )
} ) } )
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": "hidemode 3,align center center" "$layoutConstraints": "hidemode 3,align center center"

View File

@@ -646,6 +646,7 @@ TabbedPane.focusInputMap
TabbedPane.font TabbedPane.font
TabbedPane.foreground TabbedPane.foreground
TabbedPane.hasFullBorder TabbedPane.hasFullBorder
TabbedPane.hiddenTabsNavigation
TabbedPane.highlight TabbedPane.highlight
TabbedPane.hoverColor TabbedPane.hoverColor
TabbedPane.labelShift TabbedPane.labelShift