From c58f5a6ca7c4b5dc5aacd5e25ad6cc6d38c891ef Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 15 Oct 2020 00:10:07 +0200 Subject: [PATCH] TabbedPane: replaced forward/backward scrolling arrow buttons with "Show Hidden Tabs" button (issue #40) --- CHANGELOG.md | 5 + .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 502 +++++++++++++++++- .../com/formdev/flatlaf/FlatLaf.properties | 3 +- .../flatlaf/resources/Bundle.properties | 5 + .../flatlaf/resources/Bundle_de.properties | 5 + .../uidefaults/FlatDarkLaf_1.8.0_202.txt | 3 +- .../uidefaults/FlatLightLaf_1.8.0_202.txt | 3 +- .../uidefaults/FlatTestLaf_1.8.0_202.txt | 3 +- .../flatlaf/testing/FlatContainerTest.java | 47 ++ .../flatlaf/testing/FlatContainerTest.jfd | 38 +- .../flatlaf/themeeditor/FlatLafUIKeys.txt | 1 + 11 files changed, 596 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a726d1b..56a767de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ FlatLaf Change Log #### 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 `SCROLL_TAB_LAYOUT`). (issue #40) - TabbedPane: Repeat scrolling as long as arrow buttons are pressed. (issue #40) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index 983d458f..20f7579b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -20,16 +20,23 @@ import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.KeyboardFocusManager; +import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; 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.KeyEvent; import java.awt.event.MouseAdapter; @@ -41,15 +48,22 @@ import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; +import java.util.Locale; import java.util.Set; import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; import javax.swing.JTabbedPane; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.Timer; 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.UIResource; import javax.swing.plaf.basic.BasicTabbedPaneUI; @@ -100,6 +114,7 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TabbedPane.showTabSeparators boolean * @uiDefault TabbedPane.tabSeparatorsFullHeight boolean * @uiDefault TabbedPane.hasFullBorder boolean + * @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons * @uiDefault ScrollPane.smoothScrolling boolean * * @author Karl Tauber @@ -107,6 +122,10 @@ import com.formdev.flatlaf.util.UIScale; public class FlatTabbedPaneUI extends BasicTabbedPaneUI { + // hidden tabs navigation types + protected static final int MORE_TABS_BUTTON = 0; + protected static final int ARROW_BUTTONS = 1; + private static Set focusForwardTraversalKeys; private static Set focusBackwardTraversalKeys; @@ -128,9 +147,15 @@ public class FlatTabbedPaneUI protected boolean hasFullBorder; protected boolean tabsOverlapBorder; + protected int hiddenTabsNavigation = MORE_TABS_BUTTON; + + protected String moreTabsButtonToolTipText; + protected JViewport tabViewport; protected FlatWheelTabScroller wheelTabScroller; + private JButton moreTabsButton; + private Handler handler; private boolean blockRollover; @@ -140,7 +165,28 @@ public class FlatTabbedPaneUI @Override 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" ); selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" ); @@ -160,6 +206,9 @@ public class FlatTabbedPaneUI hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" ); + Locale l = tabPane.getLocale(); + moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l ); + // scale textIconGap = scale( textIconGap ); 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 @@ -225,6 +315,8 @@ public class FlatTabbedPaneUI super.installListeners(); tabPane.addMouseListener( getHandler() ); + tabPane.addMouseMotionListener( getHandler() ); + tabPane.addComponentListener( getHandler() ); if( tabViewport != null && (wheelTabScroller = createWheelTabScroller()) != null ) { // ideally we would add the mouse listeners to the viewport, but then the @@ -242,6 +334,8 @@ public class FlatTabbedPaneUI if( handler != null ) { tabPane.removeMouseListener( handler ); + tabPane.removeMouseMotionListener( handler ); + tabPane.removeComponentListener( handler ); handler = null; } @@ -272,6 +366,21 @@ public class FlatTabbedPaneUI 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 protected JButton createScrollButton( int direction ) { return new FlatScrollableTabButton( direction ); @@ -353,6 +462,19 @@ public class FlatTabbedPaneUI 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 protected void paintText( Graphics g, int tabPlacement, Font font, FontMetrics metrics, 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 ) { int run = getRunForTab( tabPane.getTabCount(), tabIndex ); return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex; @@ -592,6 +751,170 @@ public class FlatTabbedPaneUI 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 -------------------------------------- protected class FlatScrollableTabButton @@ -612,11 +935,21 @@ public class FlatTabbedPaneUI @Override 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(); - if( direction == WEST || direction == EAST ) - return new Dimension( size.width, Math.max( size.height, maxTabHeight ) ); - else - return new Dimension( Math.max( size.width, maxTabWidth ), size.height ); + if( direction == WEST || direction == EAST ) { + return new Dimension( + halfSize ? ((size.width / 2) + scale( 4 )) : size.width, + Math.max( size.height, maxTabHeight ) ); + } else { + return new Dimension( + Math.max( size.width, maxTabWidth ), + halfSize ? ((size.height / 2) + scale( 4 )) : size.height ); + } } @Override @@ -871,11 +1204,8 @@ public class FlatTabbedPaneUI return; scrolled = false; - int selectedIndex = tabPane.getSelectedIndex(); - if( selectedIndex >= 0 ) { - Rectangle tabBounds = getTabBounds( tabPane, selectedIndex ); - tabViewport.scrollRectToVisible( tabBounds ); - } + // scroll selected tab into visible area + ensureSelectedTabIsVisible(); } } @@ -883,9 +1213,18 @@ public class FlatTabbedPaneUI private class Handler extends MouseAdapter - implements PropertyChangeListener + implements PropertyChangeListener, ChangeListener, ComponentListener { 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 public void mouseExited( MouseEvent e ) { @@ -895,11 +1234,41 @@ public class FlatTabbedPaneUI 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 public void propertyChange( PropertyChangeEvent e ) { - propertyChangeDelegate.propertyChange( e ); - + // invoke delegate listener 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_CONTENT_SEPARATOR: case TABBED_PANE_HAS_FULL_BORDER: @@ -909,5 +1278,112 @@ public class FlatTabbedPaneUI 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". + *

+ * 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 ); + } } } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index 6a9583c8..8559cc12 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -546,10 +546,11 @@ TabbedPane.tabInsets=4,12,4,12 TabbedPane.tabAreaInsets=0,0,0,0 TabbedPane.selectedTabPadInsets=0,0,0,0 TabbedPane.tabRunOverlay=0 -TabbedPane.tabsOverlapBorder=true +TabbedPane.tabsOverlapBorder=false TabbedPane.disabledForeground=@disabledText TabbedPane.shadow=@background TabbedPane.contentBorderInsets=null +TabbedPane.hiddenTabsNavigation=moreTabsButton #---- Table ---- diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle.properties index 9ee3fb8f..fa1d20fc 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle.properties @@ -46,3 +46,8 @@ FileChooser.refreshActionLabelText=Refresh FileChooser.newFolderActionLabelText=New Folder FileChooser.listViewActionLabelText=List FileChooser.detailsViewActionLabelText=Details + + +#---- TabbedPane ---- + +TabbedPane.moreTabsButtonToolTipText=Show Hidden Tabs diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle_de.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle_de.properties index f9034e76..5f30c8dd 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle_de.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/resources/Bundle_de.properties @@ -46,3 +46,8 @@ FileChooser.refreshActionLabelText=Aktualisieren FileChooser.newFolderActionLabelText=Neuer Ordner FileChooser.listViewActionLabelText=Liste FileChooser.detailsViewActionLabelText=Details + + +#---- TabbedPane ---- + +TabbedPane.moreTabsButtonToolTipText=Verdeckte Tabs anzeigen diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt index 18f3bfc9..cd66c85b 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -916,6 +916,7 @@ TabbedPane.focusColor #3d4b5c javax.swing.plaf.ColorUIResource [UI] TabbedPane.font [active] $defaultFont [UI] TabbedPane.foreground #bbbbbb javax.swing.plaf.ColorUIResource [UI] TabbedPane.hasFullBorder false +TabbedPane.hiddenTabsNavigation moreTabsButton TabbedPane.highlight #242424 javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #2e3133 javax.swing.plaf.ColorUIResource [UI] TabbedPane.labelShift 1 @@ -932,7 +933,7 @@ TabbedPane.tabRunOverlay 0 TabbedPane.tabSelectionHeight 3 TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabsOpaque true -TabbedPane.tabsOverlapBorder true +TabbedPane.tabsOverlapBorder false TabbedPane.textIconGap 4 TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt index c749f999..e179a14e 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -921,6 +921,7 @@ TabbedPane.focusColor #dae4ed javax.swing.plaf.ColorUIResource [UI] TabbedPane.font [active] $defaultFont [UI] TabbedPane.foreground #000000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.hasFullBorder false +TabbedPane.hiddenTabsNavigation moreTabsButton TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #d9d9d9 javax.swing.plaf.ColorUIResource [UI] TabbedPane.labelShift 1 @@ -937,7 +938,7 @@ TabbedPane.tabRunOverlay 0 TabbedPane.tabSelectionHeight 3 TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabsOpaque true -TabbedPane.tabsOverlapBorder true +TabbedPane.tabsOverlapBorder false TabbedPane.textIconGap 4 TabbedPane.underlineColor #4083c9 javax.swing.plaf.ColorUIResource [UI] TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt index 86f70157..d49664a1 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -909,6 +909,7 @@ TabbedPane.focusColor #dddddd javax.swing.plaf.ColorUIResource [UI] TabbedPane.font [active] $defaultFont [UI] TabbedPane.foreground #ff0000 javax.swing.plaf.ColorUIResource [UI] TabbedPane.hasFullBorder false +TabbedPane.hiddenTabsNavigation moreTabsButton TabbedPane.highlight #ffffff javax.swing.plaf.ColorUIResource [UI] TabbedPane.hoverColor #eeeeee javax.swing.plaf.ColorUIResource [UI] TabbedPane.labelShift 1 @@ -928,7 +929,7 @@ TabbedPane.tabSelectionHeight 3 TabbedPane.tabSeparatorColor #0000ff javax.swing.plaf.ColorUIResource [UI] TabbedPane.tabSeparatorsFullHeight false TabbedPane.tabsOpaque true -TabbedPane.tabsOverlapBorder true +TabbedPane.tabsOverlapBorder false TabbedPane.textIconGap 4 TabbedPane.underlineColor #4a88c7 javax.swing.plaf.ColorUIResource [UI] TabbedPaneUI com.formdev.flatlaf.ui.FlatTabbedPaneUI diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java index eed8036a..ad631094 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java @@ -215,6 +215,27 @@ public class FlatContainerTest 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() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JPanel panel9 = new JPanel(); @@ -244,6 +265,9 @@ public class FlatContainerTest customBorderCheckBox = new JCheckBox(); customTabsCheckBox = new JCheckBox(); hasFullBorderCheckBox = new JCheckBox(); + JLabel tabPlacementLabel = new JLabel(); + tabPlacementField = new JComboBox<>(); + tabBackForegroundCheckBox = new JCheckBox(); CellConstraints cc = new CellConstraints(); //======== this ======== @@ -356,6 +380,7 @@ public class FlatContainerTest "[fill]", // rows "[center]" + + "[]" + "[]")); //---- moreTabsCheckBox ---- @@ -411,6 +436,26 @@ public class FlatContainerTest hasFullBorderCheckBox.setText("Show content border"); hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged()); 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)); } @@ -433,6 +478,8 @@ public class FlatContainerTest private JCheckBox customBorderCheckBox; private JCheckBox customTabsCheckBox; private JCheckBox hasFullBorderCheckBox; + private JComboBox tabPlacementField; + private JCheckBox tabBackForegroundCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables //---- class Tab1Panel ---------------------------------------------------- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd index ce4551bc..437bfa6f 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd @@ -132,7 +132,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" "$columnConstraints": "[][fill][][][fill]" - "$rowConstraints": "[center][]" + "$rowConstraints": "[center][][]" } ) { name: "panel14" "opaque": false @@ -251,6 +251,40 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "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 ) { "gridY": 11 "gridWidth": 3 @@ -260,7 +294,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "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 ) { "$layoutConstraints": "hidemode 3,align center center" diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index 431096d4..a8742276 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -646,6 +646,7 @@ TabbedPane.focusInputMap TabbedPane.font TabbedPane.foreground TabbedPane.hasFullBorder +TabbedPane.hiddenTabsNavigation TabbedPane.highlight TabbedPane.hoverColor TabbedPane.labelShift