/*
* 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) 2012 Herve Guillaume, 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
*
* Herve Guillaume
* rvguillaume@hotmail.com
* FR - France
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock.wizard;
import java.awt.Dimension;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation.Orientation;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.station.span.Span;
import bibliothek.gui.dock.station.span.SpanCallback;
import bibliothek.gui.dock.station.span.SpanFactory;
import bibliothek.gui.dock.station.span.SpanMode;
import bibliothek.gui.dock.station.span.SpanUsage;
import bibliothek.gui.dock.station.split.Node;
import bibliothek.gui.dock.station.split.PutInfo;
import bibliothek.gui.dock.station.split.SplitNode;
import bibliothek.gui.dock.themes.StationSpanFactoryValue;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.wizard.WizardNodeMap.Cell;
import bibliothek.gui.dock.wizard.WizardNodeMap.Column;
/**
* The {@link WizardSpanStrategy} keeps track of the required {@link Span}s for a {@link WizardSplitDockStation}
* and adds or removes {@link Span}s when necessary.
* @author Benjamin Sigg
*/
public class WizardSpanStrategy {
private WizardSplitDockStation station;
private Span[][] cellSpans = new Span[0][0];
private Span[] columnSpans = new Span[0];
private StationSpanFactoryValue factory;
private int selectedColumn = -1;
private int selectedCell = -1;
public WizardSpanStrategy( WizardSplitDockStation station ){
this.station = station;
factory = new StationSpanFactoryValue( ThemeManager.SPAN_FACTORY + ".wizard", station ){
@Override
protected void changed(){
reset();
}
};
station.addDockStationListener( new DockStationAdapter(){
@Override
public void dockableAdded( DockStation station, Dockable dockable ){
checkReset();
}
@Override
public void dockableRemoved( DockStation station, Dockable dockable ){
checkReset();
}
@Override
public void dockablesRepositioned( DockStation station, Dockable[] dockables ){
checkReset();
}
});
}
/**
* Updates the current {@link SpanFactory} if necessary.
* @param controller the realm in which this strategy should work
*/
public void setController( DockController controller ){
factory.setController( controller );
}
private void checkReset(){
WizardNodeMap map = station.getWizardSplitLayoutManager().getMap();
Column[] columns = map.getSortedColumns();
if( columns.length != columnSpans.length-1 ){
reset();
}
else{
for( int i = 0; i < columns.length; i++ ){
if( cellSpans[i].length-1 != columns[i].getCellCount() ){
reset();
return;
}
}
}
}
/**
* Deletes and recreates all spans.
*/
public void reset(){
selectedColumn = -1;
selectedCell = -1;
WizardNodeMap map = station.getWizardSplitLayoutManager().getMap();
Column[] columns = map.getSortedColumns();
columnSpans = new Span[ columns.length+1 ];
cellSpans = new Span[ columns.length ][];
Callback columnCallback = new Callback( true );
Callback cellCallback = new Callback( false );
int gap = station.getDividerSize();
for( int i = 0; i < columnSpans.length; i++ ){
columnSpans[i] = factory.create( columnCallback );
}
for( int i = 1; i < columns.length; i++ ){
columnSpans[i].configureSize( SpanMode.OFF, gap );
}
for( int i = 0; i < columns.length; i++ ){
int cellCount = columns[i].getCellCount();
cellSpans[i] = new Span[ cellCount+1 ];
for( int j = 0; j < cellSpans[i].length; j++ ){
cellSpans[i][j] = factory.create( cellCallback );
}
for( int j = 1; j < cellCount; j++ ){
cellSpans[i][j].configureSize( SpanMode.OFF, gap );
}
}
}
/**
* Mutates the {@link Span}s such that <code>info</code> shows up.
* @param info the current drop information or <code>null</code>
*/
public void setPut( PutInfo info ){
if( info == null || info.getCombinerTarget() != null ){
setPut( -1, -1 );
}
else if( info.getNode() == null ){
setPut( 0, -1 );
}
else{
WizardNodeMap map = station.getWizardSplitLayoutManager().getMap();
SplitNode node = info.getNode();
node = traverseDown( node );
Column column = map.getColumn( node );
Dimension size = info.getDockable().getComponent().getPreferredSize();
int width = size.width;
int height = size.height;
if( station.getSide().getHeaderOrientation() == Orientation.HORIZONTAL ){
for( Span span : columnSpans ){
span.configureSize( SpanMode.OPEN, width );
}
for( Span[] array : cellSpans ){
for( Span span : array ){
span.configureSize( SpanMode.OPEN, height );
}
}
if( info.getPut() == PutInfo.Put.LEFT ){
setPut( column.getIndex(), -1 );
}
else if( info.getPut() == PutInfo.Put.RIGHT ){
setPut( column.getIndex()+1, -1 );
}
else if( info.getPut() == PutInfo.Put.TOP ){
Cell cell = column.getLeftmostCell( node );
setPut( column.getIndex(), cell.getIndex() );
}
else if( info.getPut() == PutInfo.Put.BOTTOM ){
Cell cell = column.getRightmostCell( node );
setPut( column.getIndex(), cell.getIndex()+1 );
}
}
else{
for( Span span : columnSpans ){
span.configureSize( SpanMode.OPEN, height );
}
for( Span[] array : cellSpans ){
for( Span span : array ){
span.configureSize( SpanMode.OPEN, width );
}
}
if( info.getPut() == PutInfo.Put.LEFT ){
Cell cell = column.getLeftmostCell( node );
setPut( column.getIndex(), cell.getIndex() );
}
else if( info.getPut() == PutInfo.Put.RIGHT ){
Cell cell = column.getRightmostCell( node );
setPut( column.getIndex(), cell.getIndex()+1 );
}
else if( info.getPut() == PutInfo.Put.TOP ){
setPut( column.getIndex(), -1 );
}
else if( info.getPut() == PutInfo.Put.BOTTOM ){
setPut( column.getIndex()+1, -1 );
}
}
}
}
private SplitNode traverseDown( SplitNode node ){
while( node instanceof Node ){
Node n = (Node)node;
boolean left = n.getLeft().isVisible();
boolean right = n.getRight().isVisible();
if( !left && right ){
node = n.getRight();
}
else if( left && !right ){
node = n.getLeft();
}
else{
break;
}
}
return node;
}
private void setPut( int column, int cell ){
selectedColumn = column;
selectedCell = cell;
for( int i = 0; i < columnSpans.length; i++ ){
if( i == column && cell == -1 ){
columnSpans[i].mutate( SpanMode.OPEN );
}
else{
columnSpans[i].mutate( SpanMode.OFF );
}
}
for( int i = 0; i < cellSpans.length; i++ ){
for( int j = 0; j < cellSpans[i].length; j++ ){
if( i == column && j == cell ){
cellSpans[i][j].mutate( SpanMode.OPEN );
}
else{
cellSpans[i][j].mutate( SpanMode.OFF );
}
}
}
}
/**
* Immediately resets all {@link Span}s to have a size of <code>0</code>.
*/
public void unsetPut(){
selectedColumn = -1;
selectedCell = -1;
for( Span span : columnSpans ){
span.set( SpanMode.OFF );
}
for( Span[] array : cellSpans ){
for( Span span : array ){
span.set( SpanMode.OFF );
}
}
}
/**
* Gets the size of the currently selected {@link Span} according to
* {@link #setPut(PutInfo)}. If there is no {@link Span} selected, then
* this method returns the standard size of a gap.
* @return the size of the currently selected gap
*/
public int getGap(){
if( selectedColumn == -1 ){
return station.getDividerSize();
}
if( selectedCell == -1 ){
if( selectedColumn >= columnSpans.length ){
return station.getDividerSize();
}
else{
return getSize( columnSpans[selectedColumn] );
}
}
if( selectedColumn >= cellSpans.length ){
return station.getDividerSize();
}
Span[] array = cellSpans[selectedColumn];
if( selectedCell >= array.length ){
return station.getDividerSize();
}
return getSize( array[selectedCell] );
}
/**
* Gets the size of the gap left of <code>column</code>.
* @param column the column whose gap to its predecessor is requested
* @return the size of the gap
*/
public int getGap( int column ){
if( column >= columnSpans.length ){
return 0;
}
return getSize( columnSpans[column] );
}
/**
* Gets the size of the gap between <code>cell</code> and its predecessor.
* @param column the column in which to search
* @param cell the cell to search
* @return the gap before <code>cell</code>
*/
public int getGap( int column, int cell ){
if( column >= cellSpans.length ){
return 0;
}
if( cell >= cellSpans[column].length ){
return 0;
}
return getSize( cellSpans[column][cell] );
}
/**
* Gets the current size of <code>span</code>. May be overridden by subclasses to influence the
* size of a span.
* @param span the size of <code>span</code>
* @return the size of the span
*/
protected int getSize( Span span ){
return span.getSize();
}
/**
* Gets the size of the gap that is currently to be used by <code>node</code>
* @param node the node whose inner gap is requested
* @param map detailed information about the layout of this station
* @return the size of the inner gap
*/
public int getGap( Node node, WizardNodeMap map ){
Column column = map.getColumn( node );
if( column == null ){
return 0;
}
SplitNode root = column.getRoot().getParent();
while( root != null ){
if( root == node ){
return getGap( column.getIndex() );
}
root = root.getParent();
}
Cell cell = column.getLeftmostCell( node.getRight() );
if( cell == null ){
return getGap( column.getIndex() );
}
else{
return getGap( column.getIndex(), cell.getIndex() );
}
}
/**
* Callback for a set of {@link Span}s used by a {@link WizardSpanStrategy}.
* @author Benjamin Sigg
*/
private class Callback implements SpanCallback{
private boolean column;
public Callback( boolean column ){
this.column = column;
}
@Override
public DockStation getStation(){
return station;
}
@Override
public boolean isHorizontal(){
if( column ){
return station.getSide().getHeaderOrientation() == Orientation.HORIZONTAL;
}
else{
return station.getSide().getColumnOrientation() == Orientation.HORIZONTAL;
}
}
@Override
public boolean isVertical(){
return !isHorizontal();
}
@Override
public void resized(){
station.revalidateOutside();
}
@Override
public SpanUsage getUsage(){
return SpanUsage.INSERTING;
}
}
}