/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2011 Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock.station.stack.tab;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.station.stack.tab.layouting.TabPlacement;
import bibliothek.gui.dock.themes.basic.action.buttons.ButtonPanel;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.util.swing.OrientedLabel;
/**
* A {@link LayoutManager} that can be used by {@link Component}s that show an
* {@link OrientedLabel} and a {@link ButtonPanel}.
* @author Benjamin Sigg
*/
public class TabComponentLayoutManager implements LayoutManager{
/** the free space around the content to the semi open sides */
private int freeSpaceToSideBorder;
/** the free space around the content to the closed side */
private int freeSpaceToParallelBorder;
/** free space between {@link #label} and {@link #actions} */
private int freeSpaceBetweenLabelAndActions;
/** the free space around the content to the open side */
private int freeSpaceToOpenSide;
/** insets to be added to the label */
private Insets labelInsets = new Insets( 0, 0, 0, 0 );
/** insets to be added to the actions */
private Insets actionInsets = new Insets( 0, 0, 0, 0 );
/** the current layout */
private TabPlacement orientation;
private OrientedLabel label;
private ButtonPanel actions;
/** details about the layout */
private TabConfiguration configuration;
/**
* Creates a new layout manager.
* @param label the label shown on the tab
* @param panel the actions shown on the tab
* @param configuration the exact look and behavior of the tab
*/
public TabComponentLayoutManager( OrientedLabel label, ButtonPanel panel, TabConfiguration configuration ){
this.label = label;
this.actions = panel;
setOrientation( TabPlacement.TOP_OF_DOCKABLE );
setConfiguration( configuration );
}
/**
* Gets the panels showing the actions
* @return the actions, not <code>null</code>
*/
public ButtonPanel getActions(){
return actions;
}
/**
* Gets the label showing icon and text
* @return the label, not <code>null</code>
*/
public OrientedLabel getLabel(){
return label;
}
/**
* Gets the current configuration of the tab
* @return the configuration, not <code>null</code>
*/
public TabConfiguration getConfiguration(){
return configuration;
}
/**
* Changes the look and behavior of the tab.
* @param configuration the new configuration to use, not <code>null</code>
*/
public void setConfiguration( TabConfiguration configuration ){
if( configuration == null ){
throw new IllegalArgumentException( "configuration must not be null" );
}
this.configuration = configuration;
label.revalidate();
}
/**
* Sets the size of the free space between content and the open side.
* @param freeSpaceToOpenSide the size
*/
public void setFreeSpaceToOpenSide( int freeSpaceToOpenSide ){
this.freeSpaceToOpenSide = freeSpaceToOpenSide;
}
/**
* Gets the size of the open side.
* @return the size
* @see #setFreeSpaceToOpenSide(int)
*/
public int getFreeSpaceToOpenSide(){
return freeSpaceToOpenSide;
}
/**
* Sets the size of the gap that is between the label (icon and text) and the
* {@link DockAction}s (if there are any).
* @param freeSpaceBetweenLabelAndActions the size
*/
public void setFreeSpaceBetweenLabelAndActions( int freeSpaceBetweenLabelAndActions ){
this.freeSpaceBetweenLabelAndActions = freeSpaceBetweenLabelAndActions;
}
/**
* Gets the size of the gap between text/icon and actions.
* @return the size
* @see #setFreeSpaceBetweenLabelAndActions(int)
*/
public int getFreeSpaceBetweenLabelAndActions(){
return freeSpaceBetweenLabelAndActions;
}
/**
* Sets the size of the free space between content and the border at the side
* of the {@link Dockable}.
* @param freeSpaceToParallelBorder the size
*/
public void setFreeSpaceToParallelBorder( int freeSpaceToParallelBorder ){
this.freeSpaceToParallelBorder = freeSpaceToParallelBorder;
}
/**
* Gets the size of the border at the side of the {@link Dockable}
* @return the size
* @see #setFreeSpaceToParallelBorder(int)
*/
public int getFreeSpaceToParallelBorder(){
return freeSpaceToParallelBorder;
}
/**
* Sets the size of the free space between the borders that are on the same line as
* the text/icon and the actions.
* @param freeSpaceToSideBorder the size
*/
public void setFreeSpaceToSideBorder( int freeSpaceToSideBorder ){
this.freeSpaceToSideBorder = freeSpaceToSideBorder;
}
/**
* Gets the size of the free space between the borders that are on the same line as
* the text/icon and the actions.
* @return the size
* @see #setFreeSpaceToSideBorder(int)
*/
public int getFreeSpaceToSideBorder(){
return freeSpaceToSideBorder;
}
/**
* Sets the space that should be left empty around the label.
* @param labelInsets the empty space, not <code>null</code>
*/
public void setLabelInsets( Insets labelInsets ){
if( labelInsets == null ){
throw new IllegalArgumentException( "insets must not be null" );
}
this.labelInsets = new Insets( labelInsets.top, labelInsets.left, labelInsets.bottom, labelInsets.right );
}
/**
* Gets the empty space around the label.
* @return the empty space
*/
public Insets getLabelInsets(){
return labelInsets;
}
/**
* Sets the empty space around the actions.
* @param actionInsets the empty space, not <code>null</code>
*/
public void setActionInsets( Insets actionInsets ){
if( labelInsets == null ){
throw new IllegalArgumentException( "insets must not be null" );
}
this.actionInsets = new Insets( actionInsets.top, actionInsets.left, actionInsets.bottom, actionInsets.right );
}
/**
* Gets the empty space around the actions.
* @return the empty space
*/
public Insets getActionInsets(){
return actionInsets;
}
public void setOrientation( TabPlacement orientation ){
if( orientation == null )
throw new IllegalArgumentException( "orientation must not be null" );
if( this.orientation != orientation ){
this.orientation = orientation;
label.setHorizontal( orientation.isHorizontal() );
switch( orientation ){
case BOTTOM_OF_DOCKABLE:
actions.setOrientation( DockTitle.Orientation.SOUTH_SIDED );
break;
case LEFT_OF_DOCKABLE:
actions.setOrientation( DockTitle.Orientation.EAST_SIDED );
break;
case RIGHT_OF_DOCKABLE:
actions.setOrientation( DockTitle.Orientation.WEST_SIDED );
break;
case TOP_OF_DOCKABLE:
actions.setOrientation( DockTitle.Orientation.NORTH_SIDED );
break;
}
}
}
/**
* Gets the current position of the tab in relation to the {@link Dockable}s.
* @return the location of the tab
*/
public TabPlacement getOrientation(){
return orientation;
}
public void addLayoutComponent( String name, Component comp ){
if( comp != label && comp != actions ){
throw new IllegalArgumentException( "must add either label or panel" );
}
}
public Dimension preferredLayoutSize( Container parent ){
Dimension size = label.getPreferredSize();
Dimension result;
if( orientation.isHorizontal() ){
result = new Dimension(
size.width+2*freeSpaceToSideBorder+labelInsets.left+labelInsets.right,
size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+labelInsets.top+labelInsets.bottom );
if( actions.hasActions() ){
result.width += freeSpaceBetweenLabelAndActions;
size = actions.getPreferredSize();
result.width += size.width+actionInsets.left+actionInsets.right;
result.height = Math.max( result.height, size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.top+actionInsets.bottom );
}
else{
result.width += actionInsets.right;
result.height = Math.max( result.height, size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.bottom );
}
}
else{
result = new Dimension(
size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+labelInsets.left+labelInsets.right,
size.height+2*freeSpaceToSideBorder+labelInsets.top+labelInsets.bottom );
if( actions.hasActions() ){
result.height += freeSpaceBetweenLabelAndActions;
size = actions.getPreferredSize();
result.height += size.height+actionInsets.top+actionInsets.bottom;
result.width = Math.max( result.width, size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.left+actionInsets.right );
}
else{
result.height += actionInsets.bottom;
result.width = Math.max( result.width, size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.right );
}
}
return result;
}
public Dimension minimumLayoutSize( Container parent ){
Dimension size = label.getMinimumSize();
Dimension result;
if( orientation.isHorizontal() ){
result = new Dimension(
size.width+2*freeSpaceToSideBorder+labelInsets.left+labelInsets.right,
size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+labelInsets.top+labelInsets.bottom );
if( actions.hasActions() ){
result.width += freeSpaceBetweenLabelAndActions;
size = actions.getMinimumSize();
result.width += size.width+actionInsets.left+actionInsets.right;
result.height = Math.max( result.height, size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.top+actionInsets.bottom );
}
else{
result.width += actionInsets.right;
result.height = Math.max( result.height, size.height+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.bottom );
}
}
else{
result = new Dimension(
size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+labelInsets.left+labelInsets.right,
size.height+2*freeSpaceToSideBorder+labelInsets.top+labelInsets.bottom );
if( actions.hasActions() ){
result.height += freeSpaceBetweenLabelAndActions;
size = actions.getMinimumSize();
result.height += size.height+actionInsets.top+actionInsets.bottom;
result.width = Math.max( result.width, size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.left+actionInsets.right );
}
else{
result.height += actionInsets.bottom;
result.width = Math.max( result.width, size.width+freeSpaceToOpenSide+freeSpaceToParallelBorder+actionInsets.right );
}
}
return result;
}
public void layoutContainer( Container parent ){
int width = parent.getWidth();
int height = parent.getHeight();
Dimension actionsSize;
boolean showActions = shouldShowActions( parent );
if( showActions ){
actions.setVisible( true );
actionsSize = actions.getPreferredSize();
actionsSize = new Dimension(
actionsSize.width+actionInsets.left+actionInsets.right,
actionsSize.height+actionInsets.top+actionInsets.bottom );
}
else{
actions.setVisible( false );
if( configuration.isHiddenActionUsingSpace() ){
actionsSize = new Dimension(
actionInsets.right,
actionInsets.bottom );
}
else{
actionsSize = new Dimension( 0, 0 );
}
}
switch( orientation ){
case TOP_OF_DOCKABLE:
label.setBounds(
freeSpaceToSideBorder+labelInsets.left,
freeSpaceToOpenSide+labelInsets.top,
labelSize( parent, width-2*freeSpaceToSideBorder - actionsSize.width - labelInsets.left - labelInsets.right, freeSpaceToSideBorder+labelInsets.left, showActions ),
height-freeSpaceToOpenSide-freeSpaceToParallelBorder - labelInsets.top - labelInsets.bottom );
if( showActions ){
int actionsHeight = Math.min( actionsSize.height, height - freeSpaceToOpenSide - freeSpaceToParallelBorder );
int delta = height-freeSpaceToOpenSide-freeSpaceToParallelBorder-actionsHeight-actionInsets.top-actionInsets.bottom;
actions.setBounds(
Math.max( 0, width-freeSpaceToOpenSide-actionsSize.width + actionInsets.left ),
height-actionsHeight-freeSpaceToParallelBorder-delta/2 + actionInsets.top,
actionsSize.width - actionInsets.left - actionInsets.right,
actionsHeight - actionInsets.top - actionInsets.bottom );
}
break;
case BOTTOM_OF_DOCKABLE:
label.setBounds(
freeSpaceToSideBorder + labelInsets.left,
freeSpaceToParallelBorder + labelInsets.top,
labelSize( parent, width-2*freeSpaceToSideBorder-actionsSize.width - labelInsets.left - labelInsets.right, freeSpaceToSideBorder + labelInsets.left, showActions ),
height-freeSpaceToOpenSide-freeSpaceToParallelBorder - labelInsets.top - labelInsets.bottom );
if( showActions ){
int actionsHeight = Math.min( actionsSize.height, height-freeSpaceToOpenSide-freeSpaceToParallelBorder );
int delta = height-freeSpaceToOpenSide-freeSpaceToParallelBorder-actionsHeight-actionInsets.top-actionInsets.bottom;
actions.setBounds(
Math.max( 0, width-freeSpaceToOpenSide-actionsSize.width + actionInsets.left ),
freeSpaceToParallelBorder+delta/2 + actionInsets.top,
actionsSize.width - actionInsets.left - actionInsets.right,
actionsHeight - actionInsets.top - actionInsets.bottom );
}
break;
case RIGHT_OF_DOCKABLE:
label.setBounds(
freeSpaceToParallelBorder + labelInsets.left,
freeSpaceToSideBorder + labelInsets.top,
width-freeSpaceToOpenSide-freeSpaceToParallelBorder - labelInsets.left - labelInsets.right,
labelSize( parent, height-2*freeSpaceToSideBorder-actionsSize.height - labelInsets.top - labelInsets.bottom, freeSpaceToSideBorder + labelInsets.top, showActions ) );
if( showActions ){
int actionsWidth = Math.min( actionsSize.width, width-freeSpaceToOpenSide-freeSpaceToParallelBorder );
int delta = width-freeSpaceToOpenSide-freeSpaceToParallelBorder-actionsWidth-actionInsets.left-actionInsets.right;
actions.setBounds(
freeSpaceToParallelBorder+delta/2 + actionInsets.left,
Math.max( 0, height-freeSpaceToOpenSide-actionsSize.height + actionInsets.top ),
actionsWidth - actionInsets.left - actionInsets.right,
actionsSize.height - actionInsets.top - actionInsets.bottom );
}
break;
case LEFT_OF_DOCKABLE:
label.setBounds(
freeSpaceToOpenSide + labelInsets.left,
freeSpaceToSideBorder + labelInsets.top,
width-freeSpaceToOpenSide-freeSpaceToParallelBorder - labelInsets.left - labelInsets.right,
labelSize( parent, height-2*freeSpaceToSideBorder - actionsSize.height - labelInsets.top - labelInsets.bottom, freeSpaceToSideBorder + labelInsets.top, showActions ) );
if( showActions ){
int actionsWidth = Math.min( actionsSize.width, width-freeSpaceToOpenSide-freeSpaceToParallelBorder );
int delta = width-freeSpaceToOpenSide-freeSpaceToParallelBorder-actionsWidth-actionInsets.left-actionInsets.right;
actions.setBounds(
width-actionsWidth-freeSpaceToParallelBorder-delta/2 + actionInsets.left,
Math.max( 0, height-freeSpaceToOpenSide-actionsSize.height + actionInsets.top ),
actionsWidth - actionInsets.left - actionInsets.right,
actionsSize.height - actionInsets.top - actionInsets.bottom );
}
break;
}
label.setIconHidden( !shouldShowIcon() );
}
private int labelSize( Container parent, int suggested, int start, boolean showActions ){
if( showActions ){
return suggested;
}
if( configuration.isKeepLabelBig() && label.getIcon() != null ){
if( orientation.isHorizontal() ){
return Math.min( Math.max( suggested, label.getIconOffset() + label.getIcon().getIconWidth() ), parent.getWidth() - start );
}
else{
return Math.min( Math.max( suggested, label.getIconOffset() + label.getIcon().getIconHeight() ), parent.getHeight() - start );
}
}
return suggested;
}
/**
* Using the current {@link TabConfiguration}, this method decides whether there is enough space to show
* the actions or not.
* @param parent the parent {@link Container} of the label and the actions
* @return wether the actions should be shown
*/
protected boolean shouldShowActions( Container parent ){
if( !actions.hasActions() ){
return false;
}
if( orientation.isHorizontal() ){
int minSize = -1;
int actionSize = actions.getPreferredSize().width;
int labelDelta = freeSpaceToSideBorder + freeSpaceBetweenLabelAndActions + freeSpaceToOpenSide + labelInsets.left + labelInsets.right + actionInsets.left + actionInsets.right;
switch( getConfiguration().getActionHiding() ){
case NEVER:
return true;
case NO_SPACE_LEFT:
minSize = actionSize;
break;
case TEXT_DISAPPEARING:
minSize = labelDelta + label.getPreferredSize().width + actionSize;
break;
case ICON_DISAPPEARING:
if( label.getIcon() == null ){
minSize = -1;
}
else{
minSize = labelDelta + label.getIconOffset() + label.getIcon().getIconWidth() + actionSize;
}
break;
}
return minSize <= parent.getWidth();
}
else{
int minSize = -1;
int actionSize = actions.getPreferredSize().height;
int labelDelta = freeSpaceToSideBorder + freeSpaceBetweenLabelAndActions + freeSpaceToOpenSide + labelInsets.top + labelInsets.bottom + actionInsets.top + actionInsets.bottom;
switch( getConfiguration().getActionHiding() ){
case NEVER:
return true;
case NO_SPACE_LEFT:
minSize = actionSize;
break;
case TEXT_DISAPPEARING:
minSize = labelDelta + label.getPreferredSize().height + actionSize;
break;
case ICON_DISAPPEARING:
if( label.getIcon() == null ){
minSize = -1;
}
else{
minSize = labelDelta + label.getIconOffset() + label.getIcon().getIconHeight() + actionSize;
}
break;
}
return minSize <= parent.getHeight();
}
}
/**
* Tells whether the icon should be shown for the current size of {@link #getLabel() the label}.
* @return whether to show the icon
*/
protected boolean shouldShowIcon(){
if( label.getIcon() == null ){
return true;
}
if( orientation.isHorizontal() ){
switch( getConfiguration().getIconHiding() ){
case NEVER:
return true;
case NO_SPACE_LEFT:
return label.getWidth() >= label.getIconOffset() + label.getIcon().getIconWidth();
case TEXT_DISAPPEARING:
return label.getWidth() >= label.getPreferredSize().getWidth();
}
}
else{
switch( getConfiguration().getIconHiding() ){
case NEVER:
return true;
case NO_SPACE_LEFT:
return label.getHeight() >= label.getIconOffset() + label.getIcon().getIconHeight();
case TEXT_DISAPPEARING:
return label.getHeight() >= label.getPreferredSize().getHeight();
}
}
return true;
}
public void removeLayoutComponent( Component comp ){
throw new IllegalArgumentException( "must not remove any components" );
}
}