/*
* 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) 2007 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.split;
import java.util.HashSet;
import java.util.Set;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.util.Path;
/**
* Represents the internal tree of a {@link SplitDockStation}. Can be used
* to exchange the tree of a {@link SplitDockStation}. Every node or leaf is
* represented through a {@link Key}. Client code may use these keys to read
* data, or create new branches of the tree.<br>
* Each node in a {@link SplitDockStation} has a unique identifier. This {@link SplitDockTree}
* class allows to set this identifier either through {@link Key#setNodeId(long)} or
* through the non-complex methods (the methods that create only one new key).
* @author Benjamin Sigg
* @param <D> the kind of object representing a {@link Dockable}
*/
public abstract class SplitDockTree<D>{
/** the root of the tree */
private Key root;
/** the set of Dockables which already have a key */
private Set<D> dockables = new HashSet<D>();
/**
* Creates a new array of size <code>size</code> for objects of type <code>D</code>.
* @param size the size of the array
* @return the new array
*/
public abstract D[] array( int size );
/**
* Creates an array around <code>dockable</code>.
* @param dockable the element that should be put into an array
* @return the array of length 1
*/
public D[] array( D dockable ){
D[] array = array( 1 );
array[0] = dockable;
return array;
}
/**
* Sets <code>dockable</code> as root, and returns a key to the root.
* @param dockable the new root
* @return the key to the root
*/
public Key root( D dockable ){
root = put( array( dockable ));
return root;
}
/**
* Removes the root of this tree.
* @return the old root;
*/
public Key unroot(){
Key key = root;
root = null;
return key;
}
/**
* Sets <code>key</code> as the root of the tree. The root must not have
* a parent.
* @param key the key which will be the root, <code>null</code> is not allowed.
* @return <code>this</code>
*/
public SplitDockTree<D> root( Key key ){
if( key == null )
throw new IllegalArgumentException( "Key must not be null" );
if( key.getTree() != this )
throw new IllegalArgumentException( "Key is not created by this tree" );
if( key.parent != null )
throw new IllegalArgumentException( "Key has a parent, and can't be the root" );
this.root = key;
return this;
}
/**
* Creates a key for the leaf <code>dockable</code>.
* @param dockable the element for which a key is requested
* @param nodeId the unique identifier for this node, can be -1
* @return the new key
*/
public Key put( D dockable, long nodeId ){
D[] array = array( 1 );
array[0] = dockable;
return put( array, null, nodeId );
}
/**
* Creates a key for the set <code>dockables</code>.
* @param dockables the elements for which a key is requested
* @return the new key
*/
public Key put( D... dockables ){
return put( dockables, null );
}
/**
* Creates a key for the set <code>dockables</code>.
* @param dockables the elements for which a key is requested
* @param selected the element that should be selected, can be <code>null</code>
* @return the new key
*/
public Key put( D[] dockables, D selected ){
return put( dockables, selected, -1 );
}
/**
* Creates a key for the set <code>dockables</code>.
* @param dockables the elements for which a key is requested
* @param selected the element that should be selected, can be <code>null</code>
* @param nodeId a unique identifier for this node, may be -1
* @return the new key
*/
public Key put( D[] dockables, D selected, long nodeId ){
return put( dockables, selected, null, null, nodeId );
}
/**
* Creates a key for a placeholder leaf.
* @param placeholders the placeholders to store
* @param placeholderMap placeholder information of a child {@link DockStation}
* @return the new key
*/
public Key put( Path[] placeholders, PlaceholderMap placeholderMap ){
return put( placeholders, placeholderMap, -1 );
}
/**
* Creates a key for a placeholder leaf.
* @param placeholders the placeholders to store
* @param placeholderMap placeholder information of a child {@link DockStation}
* @param nodeId the unique identifier of the new node, can be -1
* @return the new key
*/
public Key put( Path[] placeholders, PlaceholderMap placeholderMap, long nodeId ){
return put( null, null, placeholders, placeholderMap, nodeId );
}
/**
* Creates a key for the set of <code>dockables</code> or the set of
* <code>placeholders</code>.
* @param dockables the elements for which a key is requested
* @param selected the element that should be selected, can be <code>null</code>
* @param placeholders the placeholders for which a key is requested
* @param placeholderMap placeholder information of a child {@link DockStation}
* @param nodeId a unique identifier for this node, may be -1
* @return the new key
*/
public Key put( D[] dockables, D selected, Path[] placeholders, PlaceholderMap placeholderMap, long nodeId ){
if( placeholders == null || placeholders.length == 0 ){
if( dockables == null )
throw new IllegalArgumentException( "Dockables must not be null" );
if( dockables.length == 0 )
throw new IllegalArgumentException( "At least one Dockable is required" );
}
if( dockables != null ){
for( D dockable : dockables ){
if( dockable == null )
throw new IllegalArgumentException( "Entries of array must not be null" );
if( !this.dockables.add( dockable ))
throw new IllegalArgumentException( "Dockable already known" );
}
}
return new Leaf( dockables, selected, placeholders, placeholderMap, nodeId );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @return a key of the combination of the two elements
*/
public Key horizontal( D left, D right ){
return horizontal( put( array( left ) ), put( array( right ) ) );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @return a key of the combination of the two elements
*/
public Key horizontal( D left, D right, double divider ){
return horizontal( put( array( left ) ), put( array( right ) ), divider );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @return a key of the combination of the two elements
*/
public Key horizontal( Key left, Key right ){
return horizontal( left, right, 0.5 );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @return a key of the combination of the two elements
*/
public Key horizontal( Key left, Key right, double divider ){
return horizontal( left, right, divider, -1 );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @param nodeId a unique identifier for this node, may be -1
* @return a key of the combination of the two elements
*/
public Key horizontal( Key left, Key right, double divider, long nodeId ){
return horizontal( left, right, divider, null, null, nodeId );
}
/**
* Adds two elements horizontally.
* @param left the left element
* @param right the right element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @param placeholders placeholders that are associated with this nodes
* @param placeholderMap placeholder information of a child {@link DockStation}
* @param nodeId a unique identifier for this node, may be -1
* @return a key of the combination of the two elements
*/
public Key horizontal( Key left, Key right, double divider, Path[] placeholders, PlaceholderMap placeholderMap, long nodeId ){
return new Node( left, right, divider, true, placeholders, placeholderMap, nodeId );
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @return a key of the combination of the two elements
*/
public Key vertical( D top, D bottom ){
return vertical( put( array( top ) ), put( array( bottom ) ));
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @return a key of the combination of the two elements
*/
public Key vertical( D top, D bottom, double divider ){
return vertical( put( array( top ) ), put( array( bottom ) ), divider );
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @return a key of the combination of the two elements
*/
public Key vertical( Key top, Key bottom ){
return vertical( top, bottom, 0.5 );
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @return a key of the combination of the two elements
*/
public Key vertical( Key top, Key bottom, double divider ){
return vertical( top, bottom, divider, -1 );
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @param nodeId a unique identifier for this node, may be -1
* @return a key of the combination of the two elements
*/
public Key vertical( Key top, Key bottom, double divider, long nodeId ){
return vertical( top, bottom, divider, null, null, nodeId );
}
/**
* Adds two elements vertically.
* @param top the top element
* @param bottom the bottom element
* @param divider how much space the first element gets in respect
* to the second element. Must be between 0 and 1.
* @param placeholders placeholders that are associated with this node
* @param placeholderMap placeholder information of a child {@link DockStation}
* @param nodeId a unique identifier for this node, may be -1
* @return a key of the combination of the two elements
*/
public Key vertical( Key top, Key bottom, double divider, Path[] placeholders, PlaceholderMap placeholderMap, long nodeId ){
return new Node( top, bottom, divider, false, placeholders, placeholderMap, nodeId );
}
/**
* Gets the root of the tree.
* @return the root, can be <code>null</code>
*/
public Key getRoot(){
return root;
}
/**
* Tells whether <code>key</code> represents a leaf or not.
* @param key the key to test
* @return <code>true</code> if <code>key</code> is a leaf
*/
public boolean isDockable( Key key ){
return key.asLeaf() != null;
}
/**
* Tells whether <code>key</code> represents a node or not.
* @param key the key to test
* @return <code>true</code> if <code>key</code> is a node
*/
public boolean isNode( Key key ){
return key.asNode() != null;
}
/**
* Tells whether <code>key</code> contains placeholders
* @param key some node or leaf
* @return <code>true</code> if there are placeholders
*/
public boolean isPlaceholder( Key key ){
return key.placeholders != null && key.placeholders.length > 0;
}
/**
* Gets the placeholders which are associated with <code>key</code>
* @param key some node or leaf
* @return the placeholders, can be <code>null</code>
*/
public Path[] getPlaceholders( Key key ){
return key.placeholders;
}
/**
* Gets the placeholder information of the child {@link DockStation} of <code>key</code>.
* @param key some node or leaf
* @return the placeholder information, can be <code>null</code>
*/
public PlaceholderMap getPlaceholderMap( Key key ){
return key.placeholderMap;
}
/**
* Gets a list of all {@link Dockable}s that are known to this tree.
* @return the list of elements
*/
public D[] getDockables(){
return dockables.toArray( array( dockables.size() ) );
}
/**
* Gets the elements that are represented by the leaf <code>key</code>.
* @param key the leaf
* @return the elements, can be <code>null</code>
*/
public D[] getDockables( Key key ){
if( !isDockable( key ))
throw new IllegalArgumentException( "Not a Dockable" );
return key.asLeaf().dockables;
}
/**
* Gets the element that is selected in this leaf.
* @param key the leaf
* @return the selected element, can be <code>null</code>
*/
public D getSelected( Key key ){
if( !isDockable( key ))
throw new IllegalArgumentException( "Not a Dockable" );
return key.asLeaf().selected;
}
/**
* Tells whether the node <code>key</code> represents a horizontal
* or a vertical node.
* @param key the node
* @return <code>true</code> if the elements are laid out horizontally,
* <code>false</code> if the are vertically
*/
public boolean isHorizontal( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().horizontal;
}
/**
* Gets the left element of the node <code>key</code>.
* @param key the node
* @return the left element
*/
public Key getLeft( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().keyA;
}
/**
* Gets the right element of the node <code>key</code>.
* @param key the node
* @return the right element
*/
public Key getRight( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().keyB;
}
/**
* Gets the top element of the node <code>key</code>.
* @param key the node
* @return the top element
*/
public Key getTop( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().keyA;
}
/**
* Gets the bottom element of the node <code>key</code>.
* @param key the node
* @return the bottom element
*/
public Key getBottom( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().keyB;
}
/**
* Gets the divider of the node <code>key</code>.
* @param key the node
* @return the divider, a number between 0 and 1
*/
public double getDivider( Key key ){
if( !isNode( key ))
throw new IllegalArgumentException( "Not a node" );
return key.asNode().divider;
}
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append( getClass().getName() + "[root=\n" );
if( root == null ){
builder.append( "null" );
}
else{
root.toString( builder, 1 );
}
builder.append( "\n]" );
return builder.toString();
}
/**
* A key that represents either a node or a leaf. Clients should not
* subclass this class.
* @author Benjamin Sigg
*/
public abstract class Key{
/** the parent of this node or leaf */
private Key parent;
/** the unique id of this node */
private long id;
/** the placeholders associated with this node */
private Path[] placeholders;
/** placeholder information about a child {@link DockStation} */
private PlaceholderMap placeholderMap;
/**
* Creates a new key
* @param placeholders the placeholders that are associated with this node
* @param placeholderMap placeholder information about a child {@link DockStation}
* @param id the unique id of this node
*/
public Key( Path[] placeholders, PlaceholderMap placeholderMap, long id ){
this.id = id;
if( placeholders != null ){
this.placeholders = placeholders.clone();
}
if( placeholderMap != null ){
this.placeholderMap = placeholderMap.copy();
}
}
/**
* Converts this key and all its children into a {@link String}.
* @param builder the builder to which the content of this key is to be added
* @param depth the depth of this key (number of parents)
*/
protected abstract void toString( StringBuilder builder, int depth );
/**
* Gets the tree which is the owner of this node or leaf.
* @return the owner
*/
public SplitDockTree<D> getTree(){
return SplitDockTree.this;
}
/**
* Gets the parent of this node or leaf.
* @return the parent, can be <code>null</code>
*/
public Key getParent(){
return parent;
}
/**
* Sets the parent of this node or leaf.
* @param parent the parent
*/
private void setParent( Key parent ){
this.parent = parent;
}
/**
* Sets the unique identifier of this node
* @param id the id or -1
*/
public void setNodeId( long id ){
this.id = id;
}
/**
* Gets the unique id of this node.
* @return the identifier or -1
*/
public long getNodeId(){
return id;
}
/**
* Gets this key as a leaf.
* @return this or <code>null</code>
*/
protected Leaf asLeaf(){
return null;
}
/**
* Gets this key as a node.
* @return this or <code>null</code>
*/
protected Node asNode(){
return null;
}
}
/**
* A {@link Key} which represents a leaf.
* @author Benjamin Sigg
*
*/
private class Leaf extends Key{
/** the Dockable that will replace this leaf */
public D[] dockables;
/** the element that is selected, can be <code>null</code> */
public D selected;
/**
* Creates a new leaf.
* @param dockables the set of dockables which will replace this leaf
* @param selected the selected element
* @param placeholders the placeholders that are associated with this node
* @param placeholderMap placeholder information for a child {@link DockStation}
* @param id the unique identifier of this node or -1
*/
public Leaf( D[] dockables, D selected, Path[] placeholders, PlaceholderMap placeholderMap, long id ){
super( placeholders, placeholderMap, id );
if( dockables != null ){
this.dockables = dockables.clone();
}
this.selected = selected;
}
@Override
public Leaf asLeaf(){
return this;
}
@Override
protected void toString( StringBuilder builder, int depth ){
for( int i = 0; i < depth; i++ ){
builder.append( "\t" );
}
builder.append( "Leaf[dockables: ").append( dockables == null ? 0 : dockables.length );
Path[] placeholders = getPlaceholders( this );
if( placeholders != null ){
builder.append( ", placeholders: " );
for( Path placeholder : placeholders ){
builder.append( placeholder );
}
}
builder.append( "]" );
}
}
/**
* A {@link Key} which represents a node in the tree.
* @author Benjamin Sigg
*/
private class Node extends Key{
/** left or top child */
public Key keyA;
/** right or bottom child */
public Key keyB;
/** location of the divider */
public double divider;
/** whether the children are horizontal or vertical laid out */
public boolean horizontal;
/**
* Creates a new node.
* @param keyA the left or top child
* @param keyB the right or bottom child
* @param divider the location of the divider
* @param horizontal the orientation of this node
* @param placeholders placeholders that are associated with this node
* @param placeholderMap placeholder information of a child {@link DockStation}
* @param id the unique identifier of this node or -1
*/
public Node( Key keyA, Key keyB, double divider, boolean horizontal, Path[] placeholders, PlaceholderMap placeholderMap, long id ){
super( placeholders, placeholderMap, id );
if( keyA.getTree() != getTree() )
throw new IllegalArgumentException( "Key of first argument belongs not to this tree" );
if( keyB.getTree() != getTree() )
throw new IllegalArgumentException( "Key of second argument belongs not to this tree" );
if( divider < 0 || divider > 1.0 )
throw new IllegalArgumentException( "Divider out of bounds, must be between 0 and 1" );
if( keyA.getParent() != null )
throw new IllegalArgumentException( "First key already has a parent" );
if( keyB.getParent() != null )
throw new IllegalArgumentException( "Second key already has a parent" );
if( keyA == keyB )
throw new IllegalArgumentException( "The arguments must not be the same object" );
if( keyA == root )
throw new IllegalArgumentException( "First argument is the root, can't be a child of any other node" );
if( keyB == root )
throw new IllegalArgumentException( "Second argument is the root, can't be a child of any other node" );
keyA.setParent( this );
keyB.setParent( this );
this.keyA = keyA;
this.keyB = keyB;
this.divider = divider;
this.horizontal = horizontal;
}
@Override
protected Node asNode(){
return this;
}
protected void toString( StringBuilder builder, int depth ){
for( int i = 0; i < depth; i++ ){
builder.append( "\t" );
}
builder.append( "Node[divider: ").append( divider );
Path[] placeholders = getPlaceholders( this );
if( placeholders != null ){
builder.append( ", placeholders: " );
for( Path placeholder : placeholders ){
builder.append( placeholder );
}
}
builder.append( "]\n" );
keyA.toString( builder, depth+1 );
builder.append( "\n" );
keyB.toString( builder, depth+1 );
}
}
}