Merge pull request #192 into master

TabbedPane custom components on left and right sides of tabs area
This commit is contained in:
Karl Tauber
2020-10-19 23:35:17 +02:00
5 changed files with 457 additions and 97 deletions

View File

@@ -13,6 +13,9 @@ FlatLaf Change Log
- 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)
- 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)
- Support painting separator line between window title and content (use UI value - Support painting separator line between window title and content (use UI value
`TitlePane.borderColor`). (issue #184) `TitlePane.borderColor`). (issue #184)

View File

@@ -264,6 +264,22 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS = "arrowButtons"; String TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS = "arrowButtons";
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.awt.Component}
*/
String TABBED_PANE_LEADING_COMPONENT = "JTabbedPane.leadingComponent";
/**
* Specifies a component that will be placed at the trailing edge of the tabs area.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.awt.Component}
*/
String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent";
/** /**
* Specifies whether all text is selected when the text component gains focus. * Specifies whether all text is selected when the text component gains focus.
* <p> * <p>

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
@@ -53,6 +54,7 @@ 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.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JViewport; import javax.swing.JViewport;
@@ -118,6 +120,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons * @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons
* @uiDefault ScrollPane.smoothScrolling boolean * @uiDefault ScrollPane.smoothScrolling boolean
* *
* @uiDefault TabbedPane.moreTabsButtonToolTipText String
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatTabbedPaneUI public class FlatTabbedPaneUI
@@ -147,7 +151,7 @@ public class FlatTabbedPaneUI
protected boolean tabSeparatorsFullHeight; protected boolean tabSeparatorsFullHeight;
protected boolean hasFullBorder; protected boolean hasFullBorder;
protected int hiddenTabsNavigation = MORE_TABS_BUTTON; private String hiddenTabsNavigationStr;
protected String moreTabsButtonToolTipText; protected String moreTabsButtonToolTipText;
@@ -155,6 +159,8 @@ public class FlatTabbedPaneUI
protected FlatWheelTabScroller wheelTabScroller; protected FlatWheelTabScroller wheelTabScroller;
private JButton moreTabsButton; private JButton moreTabsButton;
private Container leadingComponent;
private Container trailingComponent;
private Handler handler; private Handler handler;
private boolean blockRollover; private boolean blockRollover;
@@ -203,6 +209,7 @@ public class FlatTabbedPaneUI
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" ); showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" ); tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
Locale l = tabPane.getLocale(); Locale l = tabPane.getLocale();
moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l ); moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l );
@@ -267,6 +274,8 @@ public class FlatTabbedPaneUI
} }
installHiddenTabsNavigation(); installHiddenTabsNavigation();
installLeadingComponent();
installTrailingComponent();
} }
@Override @Override
@@ -274,6 +283,8 @@ public class FlatTabbedPaneUI
// uninstall hidden tabs navigation before invoking super.uninstallComponents() for // uninstall hidden tabs navigation before invoking super.uninstallComponents() for
// correct uninstallation of BasicTabbedPaneUI tab scroller support // correct uninstallation of BasicTabbedPaneUI tab scroller support
uninstallHiddenTabsNavigation(); uninstallHiddenTabsNavigation();
uninstallLeadingComponent();
uninstallTrailingComponent();
super.uninstallComponents(); super.uninstallComponents();
@@ -281,16 +292,8 @@ public class FlatTabbedPaneUI
} }
protected void installHiddenTabsNavigation() { protected void installHiddenTabsNavigation() {
// initialize here because used in installHiddenTabsNavigation() before installDefaults() was invoked if( !isScrollTabLayout() || tabViewport == null )
String hiddenTabsNavigationStr = (String) tabPane.getClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION ); return;
if( hiddenTabsNavigationStr == null )
hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" );
hiddenTabsNavigation = parseHiddenTabsNavigation( hiddenTabsNavigationStr );
if( hiddenTabsNavigation != MORE_TABS_BUTTON ||
!isScrollTabLayout() ||
tabViewport == null )
return;
// At this point, BasicTabbedPaneUI already has installed // At this point, BasicTabbedPaneUI already has installed
// TabbedPaneScrollLayout (in super.createLayoutManager()) and // TabbedPaneScrollLayout (in super.createLayoutManager()) and
@@ -317,6 +320,36 @@ public class FlatTabbedPaneUI
} }
} }
protected void installLeadingComponent() {
Object c = tabPane.getClientProperty( TABBED_PANE_LEADING_COMPONENT );
if( c instanceof Component ) {
leadingComponent = new ContainerUIResource( (Component) c );
tabPane.add( leadingComponent );
}
}
protected void uninstallLeadingComponent() {
if( leadingComponent != null ) {
tabPane.remove( leadingComponent );
leadingComponent = null;
}
}
protected void installTrailingComponent() {
Object c = tabPane.getClientProperty( TABBED_PANE_TRAILING_COMPONENT );
if( c instanceof Component ) {
trailingComponent = new ContainerUIResource( (Component) c );
tabPane.add( trailingComponent );
}
}
protected void uninstallTrailingComponent() {
if( trailingComponent != null ) {
tabPane.remove( trailingComponent );
trailingComponent = null;
}
}
@Override @Override
protected void installListeners() { protected void installListeners() {
super.installListeners(); super.installListeners();
@@ -380,6 +413,14 @@ public class FlatTabbedPaneUI
return handler; return handler;
} }
@Override
protected LayoutManager createLayoutManager() {
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT )
return new FlatTabbedPaneLayout();
return super.createLayoutManager();
}
protected LayoutManager createScrollLayoutManager( TabbedPaneLayout delegate ) { protected LayoutManager createScrollLayoutManager( TabbedPaneLayout delegate ) {
return new FlatTabbedPaneScrollLayout( delegate ); return new FlatTabbedPaneScrollLayout( delegate );
} }
@@ -445,6 +486,24 @@ public class FlatTabbedPaneUI
// Giving it large values clips painting of the cropped edge and makes it invisible. // Giving it large values clips painting of the cropped edge and makes it invisible.
currentTabAreaInsets.top = currentTabAreaInsets.left = -10000; currentTabAreaInsets.top = currentTabAreaInsets.left = -10000;
// increase insets for wrap layout if using leading/trailing components
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT ) {
if( leadingComponent != null ) {
Dimension leadingSize = leadingComponent.getPreferredSize();
if( isHorizontalTabPlacement() )
insets.left += leadingSize.width;
else
insets.top += leadingSize.height;
}
if( trailingComponent != null ) {
Dimension trailingSize = trailingComponent.getPreferredSize();
if( isHorizontalTabPlacement() )
insets.right += trailingSize.width;
else
insets.bottom += trailingSize.height;
}
}
return insets; return insets;
} }
@@ -510,10 +569,9 @@ public class FlatTabbedPaneUI
return; return;
} }
// clip title if "more tabs" button is used // clip title if our layout manager is used
// (normally this is done by invoker, but fails in this case) // (normally this is done by invoker, but fails in this case)
if( hiddenTabsNavigation == MORE_TABS_BUTTON && if( tabViewport != null &&
tabViewport != null &&
(tabPlacement == TOP || tabPlacement == BOTTOM) ) (tabPlacement == TOP || tabPlacement == BOTTOM) )
{ {
Rectangle viewRect = tabViewport.getViewRect(); Rectangle viewRect = tabViewport.getViewRect();
@@ -776,6 +834,11 @@ public class FlatTabbedPaneUI
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
} }
protected boolean isHorizontalTabPlacement() {
int tabPlacement = tabPane.getTabPlacement();
return tabPlacement == TOP || tabPlacement == BOTTOM;
}
protected boolean isSmoothScrollingEnabled() { protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() ) if( !Animator.useAnimation() )
return false; return false;
@@ -786,6 +849,13 @@ public class FlatTabbedPaneUI
return UIManager.getBoolean( "ScrollPane.smoothScrolling" ); return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
} }
protected int getHiddenTabsNavigation() {
String hiddenTabsNavigationStr = (String) tabPane.getClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION );
if( hiddenTabsNavigationStr == null )
hiddenTabsNavigationStr = this.hiddenTabsNavigationStr;
return parseHiddenTabsNavigation( hiddenTabsNavigationStr );
}
protected static int parseHiddenTabsNavigation( String str ) { protected static int parseHiddenTabsNavigation( String str ) {
if( str == null ) if( str == null )
return MORE_TABS_BUTTON; return MORE_TABS_BUTTON;
@@ -809,6 +879,12 @@ public class FlatTabbedPaneUI
runnable.run(); runnable.run();
} }
protected void ensureSelectedTabIsVisibleLater() {
EventQueue.invokeLater( () -> {
ensureSelectedTabIsVisible();
} );
}
protected void ensureSelectedTabIsVisible() { protected void ensureSelectedTabIsVisible() {
if( tabPane == null || tabViewport == null ) if( tabPane == null || tabViewport == null )
return; return;
@@ -822,6 +898,34 @@ public class FlatTabbedPaneUI
((JComponent)tabViewport.getView()).scrollRectToVisible( (Rectangle) rects[selectedIndex].clone() ); ((JComponent)tabViewport.getView()).scrollRectToVisible( (Rectangle) rects[selectedIndex].clone() );
} }
private void shiftTabs( int sx, int sy ) {
if( sx == 0 && sy == 0 )
return;
for( int i = 0; i < rects.length; i++ ) {
// fix x location in rects
rects[i].x += sx;
rects[i].y += sy;
// fix tab component location
Component c = tabPane.getTabComponentAt( i );
if( c != null )
c.setLocation( c.getX() + sx, c.getY() + sy );
}
}
//---- class ContainerUIResource ------------------------------------------
private class ContainerUIResource
extends JPanel
implements UIResource
{
private ContainerUIResource( Component c ) {
super( new BorderLayout() );
add( c );
}
}
//---- class FlatMoreTabsButton ------------------------------------------- //---- class FlatMoreTabsButton -------------------------------------------
protected class FlatMoreTabsButton protected class FlatMoreTabsButton
@@ -855,6 +959,16 @@ public class FlatTabbedPaneUI
setDirection( direction ); setDirection( direction );
} }
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
boolean horizontal = (direction == SOUTH || direction == NORTH);
int margin = scale( 8 );
return new Dimension(
size.width + (horizontal ? margin : 0),
size.height + (horizontal ? 0 : margin) );
}
@Override @Override
public void paint( Graphics g ) { public void paint( Graphics g ) {
// paint arrow button near separator line // paint arrow button near separator line
@@ -978,22 +1092,10 @@ public class FlatTabbedPaneUI
} }
@Override @Override
public Dimension getPreferredSize() { protected void fireActionPerformed( ActionEvent event ) {
// Use half width/height if "more tabs" button is used, because size of runWithOriginalLayoutManager( () -> {
// "more tabs" button is the union of the backward and forward scroll buttons. super.fireActionPerformed( event );
// 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(
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 @Override
@@ -1315,18 +1417,33 @@ public class FlatTabbedPaneUI
((FlatMoreTabsButton)moreTabsButton).updateDirection(); ((FlatMoreTabsButton)moreTabsButton).updateDirection();
break; break;
case "componentOrientation":
ensureSelectedTabIsVisibleLater();
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:
case TABBED_PANE_TAB_HEIGHT: case TABBED_PANE_TAB_HEIGHT:
case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); tabPane.repaint();
break; break;
case TABBED_PANE_HIDDEN_TABS_NAVIGATION: case TABBED_PANE_LEADING_COMPONENT:
uninstallHiddenTabsNavigation(); uninstallLeadingComponent();
installHiddenTabsNavigation(); installLeadingComponent();
tabPane.revalidate();
tabPane.repaint(); tabPane.repaint();
ensureSelectedTabIsVisibleLater();
break;
case TABBED_PANE_TRAILING_COMPONENT:
uninstallTrailingComponent();
installTrailingComponent();
tabPane.revalidate();
tabPane.repaint();
ensureSelectedTabIsVisibleLater();
break; break;
} }
} }
@@ -1347,9 +1464,7 @@ public class FlatTabbedPaneUI
@Override @Override
public void componentResized( ComponentEvent e ) { public void componentResized( ComponentEvent e ) {
// make sure that selected tab stays visible when component size changed // make sure that selected tab stays visible when component size changed
EventQueue.invokeLater( () -> { ensureSelectedTabIsVisibleLater();
ensureSelectedTabIsVisible();
} );
} }
@Override public void componentMoved( ComponentEvent e ) {} @Override public void componentMoved( ComponentEvent e ) {}
@@ -1357,10 +1472,88 @@ public class FlatTabbedPaneUI
@Override public void componentHidden( ComponentEvent e ) {} @Override public void componentHidden( ComponentEvent e ) {}
} }
//---- class FlatTabbedPaneLayout -----------------------------------------
protected class FlatTabbedPaneLayout
extends TabbedPaneLayout
{
@Override
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
Rectangle bounds = tabPane.getBounds();
Insets insets = tabPane.getInsets();
int tabPlacement = tabPane.getTabPlacement();
Insets tabAreaInsets = getTabAreaInsets( tabPlacement );
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
// layout leading and trailing components in tab area
if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
// tab area bounds
int tx = insets.left;
int ty = (tabPlacement == TOP)
? insets.top
: (bounds.height - insets.bottom - maxTabHeight - tabAreaInsets.top - tabAreaInsets.bottom);
int tw = bounds.width - insets.left - insets.right;
int th = maxTabHeight;
int tyi = ty + tabAreaInsets.top;
// layout left component
Container leftComponent = leftToRight ? leadingComponent : trailingComponent;
if( leftComponent != null ) {
int leftWidth = leftComponent.getPreferredSize().width;
leftComponent.setBounds( tx, tyi, leftWidth, th );
// reduce tab area bounds
tx += leftWidth;
tw -= leftWidth;
}
// layout right component
Container rightComponent = leftToRight ? trailingComponent : leadingComponent;
if( rightComponent != null ) {
int rightWidth = rightComponent.getPreferredSize().width;
rightComponent.setBounds( tx + tw - rightWidth, tyi, rightWidth, th );
}
// fix x-locations of tabs in right-to-left component orientation
if( !leftToRight )
shiftTabs( insets.left + tabAreaInsets.right, 0 );
} else { // LEFT, RIGHT
// tab area bounds
int tx = (tabPlacement == LEFT)
? insets.left
: (bounds.width - insets.right - maxTabWidth - tabAreaInsets.left - tabAreaInsets.right);
int ty = insets.top;
int tw = maxTabWidth;
int th = bounds.height - insets.top - insets.bottom;
int txi = tx + tabAreaInsets.left;
// layout top component
if( leadingComponent != null ) {
int topHeight = leadingComponent.getPreferredSize().height;
leadingComponent.setBounds( txi, ty, tw, topHeight );
// reduce tab area bounds
ty += topHeight;
th -= topHeight;
}
// layout bottom component
if( trailingComponent != null ) {
int bottomHeight = trailingComponent.getPreferredSize().height;
trailingComponent.setBounds( txi, ty + th - bottomHeight, tw, bottomHeight );
}
}
}
}
//---- class FlatTabbedPaneScrollLayout ----------------------------------- //---- class FlatTabbedPaneScrollLayout -----------------------------------
/** /**
* Layout manager used if "TabbedPane.hiddenTabsNavigation" is "moreTabsButton". * Layout manager used for scroll tab layout policy.
* <p> * <p>
* Although this class delegates all methods to the original layout manager * Although this class delegates all methods to the original layout manager
* {@link BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends * {@link BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends
@@ -1395,16 +1588,6 @@ public class FlatTabbedPaneUI
delegate.removeLayoutComponent( 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 @Override
public void layoutContainer( Container parent ) { public void layoutContainer( Container parent ) {
// delegate to original layout manager and let it layout tabs and buttons // delegate to original layout manager and let it layout tabs and buttons
@@ -1415,69 +1598,170 @@ public class FlatTabbedPaneUI
delegate.layoutContainer( parent ); delegate.layoutContainer( parent );
} ); } );
// check whether scroll buttons are visible, which is changed by original boolean useMoreButton = (getHiddenTabsNavigation() == MORE_TABS_BUTTON);
// layout manager depending on whether there is enough room for all tabs
boolean moreTabsButtonVisible = false; // find backward/forward scroll buttons
Rectangle buttonsBounds = null; JButton backwardButton = null;
JButton forwardButton = null;
for( Component c : tabPane.getComponents() ) { for( Component c : tabPane.getComponents() ) {
if( c instanceof FlatScrollableTabButton && c.isVisible() ) { if( c instanceof FlatScrollableTabButton ) {
moreTabsButtonVisible = true; int direction = ((FlatScrollableTabButton)c).getDirection();
if( direction == WEST || direction == NORTH )
// compute union bounds of all scroll buttons backwardButton = (JButton) c;
Rectangle r = c.getBounds(); else if( direction == EAST || direction == SOUTH )
buttonsBounds = (buttonsBounds != null) ? buttonsBounds.union( r ) : r; forwardButton = (JButton) c;
// hide scroll button
c.setVisible( false );
} }
} }
// fixes for bugs in TabbedPaneScrollLayout if( !useMoreButton && (backwardButton == null || forwardButton == null) )
if( tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM ) { return; // should never occur
Insets insets = tabPane.getInsets();
if( !tabPane.getComponentOrientation().isLeftToRight() ) {
// fixes for right-to-left, which is faulty in TabbedPaneScrollLayout.calculateTabRects()
// if tabbed pane width is smaller than total tabs width, Rectangle bounds = tabPane.getBounds();
// the x locations are not zero based Insets insets = tabPane.getInsets();
int xLastTab = rects[rects.length - 1].x; int tabPlacement = tabPane.getTabPlacement();
int offset = (xLastTab < 0) ? xLastTab : 0; Insets tabAreaInsets = getTabAreaInsets( tabPlacement );
if( offset != 0 ) { Rectangle lastRect = rects[rects.length - 1];
for( int i = 0; i < rects.length; i++ ) { Dimension moreButtonSize = useMoreButton ? moreTabsButton.getPreferredSize() : null;
// fix x location in rects Dimension backwardButtonSize = useMoreButton ? null : backwardButton.getPreferredSize();
rects[i].x -= offset; Dimension forwardButtonSize = useMoreButton ? null : forwardButton.getPreferredSize();
boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight();
boolean buttonsVisible = false;
// fix tab component location // TabbedPaneScrollLayout adds tabAreaInsets to tab coordinates,
Component c = tabPane.getTabComponentAt( i ); // but we use it to position the viewport
if( c != null ) if( tabAreaInsets.left != 0 || tabAreaInsets.top != 0 ) {
c.setLocation( c.getX() - offset, c.getY() ); // remove tabAreaInsets from tab locations
} shiftTabs( -tabAreaInsets.left, -tabAreaInsets.top );
moreTabsButtonVisible = true; // reduce preferred size of view
Component view = tabViewport.getView();
Dimension viewSize = view.getPreferredSize();
boolean horizontal = (tabPlacement == TOP || tabPlacement == BOTTOM);
view.setPreferredSize( new Dimension(
viewSize.width - (horizontal ? tabAreaInsets.left : 0),
viewSize.height - (horizontal ? 0 : tabAreaInsets.top) ) );
}
Insets tabAreaInsets = getTabAreaInsets( tabPane.getTabPlacement() ); // layout tab area
Rectangle bounds = tabViewport.getBounds(); if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
// tab area bounds
int tx = insets.left;
int ty = (tabPlacement == TOP)
? insets.top
: (bounds.height - insets.bottom - maxTabHeight - tabAreaInsets.top - tabAreaInsets.bottom);
int tw = bounds.width - insets.left - insets.right;
int th = maxTabHeight;
// compute "more tabs" button bounds int tyi = ty + tabAreaInsets.top;
int buttonWidth = moreTabsButton.getPreferredSize().width + UIScale.scale( 8 );
int buttonHeight = bounds.height - tabAreaInsets.top - tabAreaInsets.bottom;
buttonsBounds = new Rectangle( bounds.x, bounds.y + bounds.height - buttonHeight, buttonWidth, buttonHeight );
// make viewport smaller on left side so that there is room for the button // layout left component
tabViewport.setBounds( bounds.x + buttonWidth, bounds.y, bounds.width - buttonWidth, bounds.height ); Container leftComponent = leftToRight ? leadingComponent : trailingComponent;
if( leftComponent != null ) {
int leftWidth = leftComponent.getPreferredSize().width;
leftComponent.setBounds( tx, tyi, leftWidth, th );
// reduce tab area bounds
tx += leftWidth;
tw -= leftWidth;
}
// layout right component
Container rightComponent = leftToRight ? trailingComponent : leadingComponent;
if( rightComponent != null ) {
int rightWidth = rightComponent.getPreferredSize().width;
rightComponent.setBounds( tx + tw - rightWidth, tyi, rightWidth, th );
// reduce tab area bounds
tw -= rightWidth;
}
int txi = tx + tabAreaInsets.left;
int twi = tw - tabAreaInsets.left - tabAreaInsets.right;
// layout viewport and buttons
int viewportWidth = twi;
int tabsWidth = leftToRight
? (lastRect.x + lastRect.width)
: (rects[0].x + rects[0].width - lastRect.x);
if( viewportWidth < tabsWidth ) {
// need buttons
buttonsVisible = true;
int buttonsWidth = useMoreButton ? moreButtonSize.width : (backwardButtonSize.width + forwardButtonSize.width);
viewportWidth = Math.max( viewportWidth - buttonsWidth, 0 );
if( useMoreButton )
moreTabsButton.setBounds( leftToRight ? (txi + twi - buttonsWidth) : txi, tyi, moreButtonSize.width, th );
else {
backwardButton.setBounds( leftToRight ? (txi + twi - buttonsWidth) : txi, tyi, backwardButtonSize.width, th );
forwardButton.setBounds( leftToRight ? (txi + twi - forwardButtonSize.width) : (txi + backwardButtonSize.width), tyi, forwardButtonSize.width, th );
} }
} else { tabViewport.setBounds( leftToRight ? txi : (txi + buttonsWidth), tyi, viewportWidth, th );
// TabbedPaneScrollLayout.layoutContainer() uses insets.left to } else
// compute button x-location where it should use insets.right tabViewport.setBounds( txi, tyi, viewportWidth, th );
if( buttonsBounds != null && insets.left != insets.right )
buttonsBounds.x = tabPane.getWidth() - insets.right - buttonsBounds.width; if( !leftToRight ) {
// layout viewport so that we can get correct view width below
tabViewport.doLayout();
// fix x-locations of tabs so that they are right-aligned in the view
shiftTabs( tabViewport.getView().getWidth() - (rects[0].x + rects[0].width), 0 );
} }
} else { // LEFT, RIGHT
// tab area bounds
int tx = (tabPlacement == LEFT)
? insets.left
: (bounds.width - insets.right - maxTabWidth - tabAreaInsets.left - tabAreaInsets.right);
int ty = insets.top;
int tw = maxTabWidth;
int th = bounds.height - insets.top - insets.bottom;
int txi = tx + tabAreaInsets.left;
// layout top component
if( leadingComponent != null ) {
int topHeight = leadingComponent.getPreferredSize().height;
leadingComponent.setBounds( txi, ty, tw, topHeight );
// reduce tab area bounds
ty += topHeight;
th -= topHeight;
}
// layout bottom component
if( trailingComponent != null ) {
int bottomHeight = trailingComponent.getPreferredSize().height;
trailingComponent.setBounds( txi, ty + th - bottomHeight, tw, bottomHeight );
// reduce tab area bounds
th -= bottomHeight;
}
int tyi = ty + tabAreaInsets.top;
int thi = th - tabAreaInsets.top - tabAreaInsets.bottom;
// layout viewport and buttons
int viewportHeight = thi;
int tabsHeight = lastRect.y + lastRect.height;
if( viewportHeight < tabsHeight ) {
// need buttons
buttonsVisible = true;
int buttonsHeight = useMoreButton ? moreButtonSize.height : (backwardButtonSize.height + forwardButtonSize.height);
viewportHeight = Math.max( viewportHeight - buttonsHeight, 0 );
if( useMoreButton )
moreTabsButton.setBounds( txi, tyi + thi - buttonsHeight, tw, moreButtonSize.height );
else {
backwardButton.setBounds( txi, tyi + thi - buttonsHeight, tw, backwardButtonSize.height );
forwardButton.setBounds( txi, tyi + thi - forwardButtonSize.height, tw, forwardButtonSize.height );
}
}
tabViewport.setBounds( txi, tyi, tw, viewportHeight );
} }
// show/hide "more tabs" button and layout it // show/hide buttons
moreTabsButton.setVisible( moreTabsButtonVisible ); moreTabsButton.setVisible( useMoreButton && buttonsVisible );
if( buttonsBounds != null ) backwardButton.setVisible( !useMoreButton && buttonsVisible );
moreTabsButton.setBounds( buttonsBounds ); forwardButton.setVisible( !useMoreButton && buttonsVisible );
} }
} }
} }

View File

@@ -248,6 +248,28 @@ public class FlatContainerTest
tabbedPane1.setForegroundAt( 1, enabled ? Color.red : null ); tabbedPane1.setForegroundAt( 1, enabled ? Color.red : null );
} }
private void leadingComponentChanged() {
leadingTrailingComponentChanged( leadingComponentCheckBox.isSelected(), TABBED_PANE_LEADING_COMPONENT, "Leading", 4 );
}
private void trailingComponentChanged() {
leadingTrailingComponentChanged( trailingComponentCheckBox.isSelected(), TABBED_PANE_TRAILING_COMPONENT, "Trailing", 12 );
}
private void leadingTrailingComponentChanged( boolean enabled, String key, String text, int gap ) {
JTabbedPane[] tabbedPanes = new JTabbedPane[] { tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 };
for( JTabbedPane tabbedPane : tabbedPanes ) {
JComponent c = null;
if( enabled ) {
c = new JLabel( text );
c.setOpaque( true );
c.setBackground( Color.cyan );
c.setBorder( new EmptyBorder( gap, gap, gap, gap ) );
}
tabbedPane.putClientProperty( key, c );
}
}
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();
@@ -282,6 +304,8 @@ public class FlatContainerTest
JLabel hiddenTabsNavigationLabel = new JLabel(); JLabel hiddenTabsNavigationLabel = new JLabel();
hiddenTabsNavigationField = new JComboBox<>(); hiddenTabsNavigationField = new JComboBox<>();
tabBackForegroundCheckBox = new JCheckBox(); tabBackForegroundCheckBox = new JCheckBox();
leadingComponentCheckBox = new JCheckBox();
trailingComponentCheckBox = new JCheckBox();
CellConstraints cc = new CellConstraints(); CellConstraints cc = new CellConstraints();
//======== this ======== //======== this ========
@@ -395,6 +419,7 @@ public class FlatContainerTest
// rows // rows
"[center]" + "[center]" +
"[]" + "[]" +
"[]" +
"[]")); "[]"));
//---- moreTabsCheckBox ---- //---- moreTabsCheckBox ----
@@ -483,6 +508,16 @@ public class FlatContainerTest
tabBackForegroundCheckBox.setText("Tab back/foreground"); tabBackForegroundCheckBox.setText("Tab back/foreground");
tabBackForegroundCheckBox.addActionListener(e -> tabBackForegroundChanged()); tabBackForegroundCheckBox.addActionListener(e -> tabBackForegroundChanged());
panel14.add(tabBackForegroundCheckBox, "cell 4 2"); panel14.add(tabBackForegroundCheckBox, "cell 4 2");
//---- leadingComponentCheckBox ----
leadingComponentCheckBox.setText("Leading");
leadingComponentCheckBox.addActionListener(e -> leadingComponentChanged());
panel14.add(leadingComponentCheckBox, "cell 0 3");
//---- trailingComponentCheckBox ----
trailingComponentCheckBox.setText("Trailing");
trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged());
panel14.add(trailingComponentCheckBox, "cell 1 3");
} }
panel9.add(panel14, cc.xywh(1, 11, 3, 1)); panel9.add(panel14, cc.xywh(1, 11, 3, 1));
} }
@@ -508,6 +543,8 @@ public class FlatContainerTest
private JComboBox<String> tabPlacementField; private JComboBox<String> tabPlacementField;
private JComboBox<String> hiddenTabsNavigationField; private JComboBox<String> hiddenTabsNavigationField;
private JCheckBox tabBackForegroundCheckBox; private JCheckBox tabBackForegroundCheckBox;
private JCheckBox leadingComponentCheckBox;
private JCheckBox trailingComponentCheckBox;
// 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
@@ -307,6 +307,26 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 2" "value": "cell 4 2"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "leadingComponentCheckBox"
"text": "Leading"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "leadingComponentChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "trailingComponentCheckBox"
"text": "Trailing"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "trailingComponentChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 3"
} )
}, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) {
"gridY": 11 "gridY": 11
"gridWidth": 3 "gridWidth": 3