/*
* 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.Dimension;
import java.awt.Rectangle;
import java.util.HashSet;
import java.util.Set;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.station.stack.tab.MenuLineLayoutOrder.Item;
import bibliothek.gui.dock.station.stack.tab.layouting.LayoutBlock;
import bibliothek.gui.dock.station.stack.tab.layouting.MenuLayoutBlock;
import bibliothek.gui.dock.station.stack.tab.layouting.Size;
import bibliothek.gui.dock.station.stack.tab.layouting.TabPlacement;
import bibliothek.gui.dock.station.stack.tab.layouting.TabsLayoutBlock;
/**
* A possibility for a layout of tabs, menus and actions as described by the {@link MenuLineLayout}.
* @author Benjamin Sigg
*/
public class MenuLineLayoutPossibility {
private MenuLineLayoutPane pane;
private Size menuSize;
private Size infoSize;
private Size tabSize;
/**
* Creates a new layout.
* @param pane the {@link TabPane} for which this possible layout is checked
* @param tab the size of the tabs, not <code>null</code>
* @param menu the size of the menu, may be <code>null</code> to indicate
* that the menu is invisible
* @param info the size of the info panel, may be <code>null</code> if
* there is no info panel to show
*/
public MenuLineLayoutPossibility( MenuLineLayoutPane pane, Size tab, Size menu, Size info ){
this.pane = pane;
this.menuSize = menu;
this.tabSize = tab;
this.infoSize = info;
}
@Override
public String toString(){
return getClass().getSimpleName() + "@[menu=" + menuSize + ", info=" + infoSize + ", tabs=" + tabSize + "]";
}
/**
* Tells how good this layout is. As higher a score, as better a layout is. The layout which shows
* all components with their preferred size has a score of <code>1.0</code>, the layout which does not
* show anything has a score of <code>0.0</code>.
* @return the score of this layout
*/
public double getScore(){
return pane.getLayout().getStrategy().getScore( this, menuSize, infoSize, tabSize );
}
/**
* Tells whether this layout shows all items with their preferred size.
* @return whether this layout is a preferred layout
*/
public boolean isPreferred(){
if( tabSize == null || !tabSize.isPreferred() || !getPane().getTabs().isAllTabs( tabSize ) )
return false;
if( menuSize != null )
return false;
return true;
}
/**
* Gets the representation of the {@link TabPane} for which which possible layout
* is evaluated.
* @return the pane, not <code>null</code>
*/
public MenuLineLayoutPane getPane(){
return pane;
}
/**
* Gets the size this layout requires, this {@link Dimension} does not include the preferred or minimal size
* required for the {@link #getPane() tab pane}.
* @return the size
*/
public Dimension getSize(){
int width = tabSize.getWidth();
int height = tabSize.getHeight();
if( getPane().getPane().getDockTabPlacement().isHorizontal() ){
if( menuSize != null ){
width += menuSize.getWidth();
height = Math.max( height, menuSize.getHeight() );
}
if( infoSize != null ){
width += infoSize.getWidth();
height = Math.max( height, infoSize.getHeight() );
}
}
else{
if( menuSize != null ){
width = Math.max( width, menuSize.getWidth() );
height += menuSize.getHeight();
}
if( infoSize != null ){
width = Math.max( width, infoSize.getWidth() );
height += infoSize.getHeight();
}
}
return new Dimension( width, height );
}
/**
* Applies the sizes specified in this layout.
*/
public void apply(){
MenuLineLayoutPane layout = getPane();
TabPane pane = layout.getPane();
MenuLineLayoutOrder order = layout.getLayout().getFactory().createOrder( layout.getLayout(), pane );
AxisConversion conversion = layout.getLayout().getConversion( pane );
TabPlacement orientation = pane.getDockTabPlacement();
// update visibility and layout
MenuLayoutBlock menu = layout.getMenu();
LayoutBlock info = layout.getInfo();
TabsLayoutBlock tabs = layout.getTabs();
tabs.setLayout( tabSize );
if( infoSize != null && info != null )
info.setLayout( infoSize );
if( menuSize == null ){
menu.getMenu().setPaneVisible( false );
}
else{
menu.getMenu().setPaneVisible( true );
menu.setLayout( menuSize );
}
// update content of tabs
if( menuSize != null ){
Set<Dockable> dockables = new HashSet<Dockable>();
for( Dockable dockable : pane.getDockables() ){
dockables.add( dockable );
}
for( Tab tab : tabs.getTabs( tabSize ) ){
dockables.remove( tab.getDockable() );
}
TabMenu tabMenu = menu.getMenu();
for( Dockable dockable : dockables ){
pane.putInMenu( tabMenu, dockable );
}
}
// update boundaries
Rectangle available = conversion.viewToModel( pane.getAvailableArea() );
// ... determine how much space is needed for menus etc
int required;
if( orientation.isHorizontal() ){
required = tabSize.getHeight();
if( infoSize != null )
required = Math.max( required, infoSize.getHeight() );
if( menuSize != null )
required = Math.max( required, menuSize.getHeight() );
}
else{
required = tabSize.getWidth();
if( infoSize != null )
required = Math.max( required, infoSize.getWidth() );
if( menuSize != null )
required = Math.max( required, menuSize.getWidth() );
}
required = Math.max( 0, Math.min( required, available.height/2 ) );
pane.setSelectedBounds( conversion.modelToView( new Rectangle( available.x, available.y + required, available.width, available.height - required ) ) );
// distribute empty space
int[] widths = calculateWidths( orientation, order, available.width, tabSize, menuSize, infoSize );
int x = available.x;
for( MenuLineLayoutOrder.Item item : order ){
int availableWidth = 0;
Size size = null;
switch( item ){
case TABS:
availableWidth = widths[0];
size = tabSize;
break;
case MENU:
availableWidth = widths[1];
size = menuSize;
break;
case INFO:
availableWidth = widths[2];
if( info != null ){
size = infoSize;
}
break;
}
if( size != null ){
int itemWidth = calculateWidth( order, item, size, availableWidth, orientation );
int deltaX = calculateDeltaX( order, item, itemWidth, availableWidth );
switch( item ){
case INFO:
int reqDelta;
if( orientation.isHorizontal() ){
reqDelta = Math.max( 0, required - infoSize.getHeight() );
}
else{
reqDelta = Math.max( 0, required - infoSize.getWidth() );
}
Rectangle infoBounds = new Rectangle( x + deltaX, available.y+reqDelta/2, itemWidth, required-reqDelta );
x += availableWidth;
infoBounds = conversion.modelToView( infoBounds );
info.setBounds( infoBounds.x, infoBounds.y, infoBounds.width, infoBounds.height );
break;
case MENU:
Rectangle menuBounds = new Rectangle( x + deltaX, available.y, itemWidth, required );
x += availableWidth;
menuBounds = conversion.modelToView( menuBounds );
menu.setBounds( menuBounds.x, menuBounds.y, menuBounds.width, menuBounds.height );
break;
case TABS:
Rectangle tabBounds = new Rectangle( x + deltaX, available.y, itemWidth, required );
tabBounds = conversion.modelToView( tabBounds );
x += availableWidth;
tabs.setBounds( tabBounds.x, tabBounds.y, tabBounds.width, tabBounds.height );
break;
}
}
}
}
private int calculateWidth( MenuLineLayoutOrder order, Item item, Size size, int width, TabPlacement orientation ){
int expected;
if( orientation.isHorizontal() ){
expected = size.getWidth();
}
else{
expected = size.getHeight();
}
int overflow = width - expected;
if( overflow <= 0 ){
return width;
}
return expected + (int)(order.getFill( item ) * overflow);
}
private int calculateDeltaX( MenuLineLayoutOrder order, Item item, int itemWidth, int availableWidth ){
int space = availableWidth - itemWidth;
if( space <= 0 ){
return 0;
}
return (int)(space * order.getAlignment( item ));
}
/**
* Calculates how much space each item receives.
* @param orientation the orientation of the layout
* @param order constraints of the items
* @param available the total available space
* @param tabSize the size required for the tabs
* @param menuSize the size required for the menu, can be <code>null</code>
* @param infoSize the size required for the info component, can be <code>null</code>
* @return the widths, where 0 is tabs, 1 is menus and 2 is info components. The sum of the widths equal to <code>available</code>
*/
private int[] calculateWidths( TabPlacement orientation, MenuLineLayoutOrder order, int available, Size tabSize, Size menuSize, Size infoSize ){
int[] result = new int[3];
// ... check whether there is enough space for all items
int space, tabSpace, menuSpace = 0, infoSpace = 0;
float weightTabs = order.getWeight( Item.TABS );
float weightInfo = 0;
float weightMenu = 0;
float totalWeight = weightTabs;
if( orientation.isHorizontal() ){
tabSpace = tabSize.getWidth();
space = tabSpace;
if( infoSize != null ){
infoSpace = infoSize.getWidth();
weightInfo = order.getWeight( Item.INFO );
space += infoSpace;
totalWeight += weightInfo;
}
if( menuSize != null ){
menuSpace = menuSize.getWidth();
space += menuSpace;
weightMenu = order.getWeight( Item.MENU );
totalWeight += weightMenu;
}
}
else{
tabSpace = tabSize.getHeight();
space = tabSpace;
if( infoSize != null ){
infoSpace = infoSize.getHeight();
space += infoSpace;
weightInfo = order.getWeight( Item.INFO );
totalWeight += weightInfo;
}
if( menuSize != null ){
menuSpace = menuSize.getHeight();
space += menuSpace;
weightMenu = order.getWeight( Item.MENU );
totalWeight += weightMenu;
}
}
int overflowSpace = available - space;
float tabsWeight = totalWeight == 0 ? 0 : (weightTabs / totalWeight);
float menuWeight = totalWeight == 0 ? 0 : (weightMenu / totalWeight);
float infoWeight = totalWeight == 0 ? 0 : (weightInfo / totalWeight);
int visibleItems = 1;
if( overflowSpace >= 0 ){
result[0] = (int)(tabSpace + tabsWeight * overflowSpace);
if( menuSize != null ){
result[1] = (int)(menuSpace + menuWeight * overflowSpace);
visibleItems++;
}
if( infoSize != null ){
result[2] = (int)(infoSpace + infoWeight * overflowSpace);
visibleItems++;
}
}
else{
float[] maximums = { tabSpace, menuSpace, infoSpace };
float[] claims = { available * weightTabs, available * weightMenu, available * weightInfo };
if( menuSize == null ){
claims[1] = -1;
}
if( infoSize == null ){
claims[2] = -1;
}
for( int i = 0; i < available; i++ ){
float maxClaim = -1;
int maxIndex = -1;
for( int c = 0; c < 3; c++ ){
if( claims[c] > maxClaim ){
maxClaim = claims[c];
maxIndex = c;
}
}
if( maxIndex >= 0 ){
claims[maxIndex] -= 1;
if( claims[maxIndex] < 0 ){
claims[maxIndex] = -0.5f;
}
result[maxIndex] += 1;
if( result[maxIndex] >= maximums[maxIndex] ){
claims[maxIndex] = -1;
}
}
}
if( menuSize != null ){
visibleItems++;
}
if( infoSize != null ){
visibleItems++;
}
}
// make sure sum equals available
int sum = 0;
for( int r : result ){
sum += r;
}
int delta = available - sum;
if( delta != 0 ){
// distribute equally, if there is a pixel too much the tabs gets them
result[0] += delta / visibleItems;
if( menuSize != null ){
result[1] += delta / visibleItems;
}
if( infoSize != null ){
result[2] += delta/ visibleItems;
}
delta -= visibleItems * (delta / visibleItems);
result[0] += delta;
}
return result;
}
}