/*
* 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.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.accept.DockAcceptance;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.station.Combiner;
import bibliothek.gui.dock.station.DockableDisplayer;
import bibliothek.gui.dock.station.support.CombinerSource;
import bibliothek.gui.dock.station.support.CombinerTarget;
import bibliothek.gui.dock.station.support.Enforcement;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.util.Path;
/**
* The internal representation of a {@link SplitDockStation} is a tree. The subclasses of SplitNode build this tree.
* @author Benjamin Sigg
*/
public abstract class SplitNode{
/** Parent node of this node */
private SplitNode parent;
/** Bounds of this node on the station */
protected double x, y, width, height;
/** Internal access to the owner-station */
private SplitDockAccess access;
/** keys for {@link Dockable}s that are not visible but linked with this node */
private Set<Path> placeholders;
/** advanced placeholder information about a {@link DockStation} that was child of this node */
private PlaceholderMap placeholderMap;
/** a (hopefully) unique id for this node */
private long id;
/** whether {@link #ensureIdUnique()} was invoked since the last call of {@link #ensureIdUniqueAsync()} */
private boolean idChecked = true;
/**
* Creates a new SplitNode.
* @param access the access to the owner of this node. Must not be <code>null</code>
* @param id the unique id of this node, -1 indicates that the id must be created
*/
protected SplitNode( SplitDockAccess access, long id ){
if( access == null )
throw new IllegalArgumentException( "Access must not be null" );
this.access = access;
if( id < 0 )
this.id = access.uniqueID();
else
this.id = id;
}
/**
* Called if a child of this node changed.
*/
protected void treeChanged(){
if( parent != null ){
parent.treeChanged();
}
}
/**
* Gets the station this node belongs to.
* @return the station
*/
public SplitDockStation getStation(){
return access.getOwner();
}
/**
* Gets all the keys that are stored in this placeholder
* @return all the keys
*/
public Path[] getPlaceholders(){
if( placeholders == null )
return new Path[]{};
return placeholders.toArray( new Path[ placeholders.size() ] );
}
/**
* Stores an additional placeholder in this node. Nothing happens if <code>placeholder</code>
* is already known to this node.
* @param placeholder the additional placeholder
*/
public void addPlaceholder( Path placeholder ){
if( placeholders == null ){
placeholders = new HashSet<Path>();
}
placeholders.add( placeholder );
}
/**
* Tells whether this node is associated with at least one placeholder.
* @return whether there is at least one placeholder
*/
public boolean hasPlaceholders(){
return placeholders != null && !placeholders.isEmpty();
}
/**
* Tells whether this node contains <code>placeholder</code>.
* @param placeholder the placeholder to search
* @return <code>true</code> if <code>placeholder</code> was found
*/
public boolean hasPlaceholder( Path placeholder ){
if( placeholders == null )
return false;
return placeholders.contains( placeholder );
}
/**
* Sets all the placeholders of this node
* @param placeholders all the placeholders, can be <code>null</code> or empty
*/
public void setPlaceholders( Path[] placeholders ){
if( this.placeholders != null ){
this.placeholders.clear();
}
if( placeholders != null ){
for( Path placeholder : placeholders ){
addPlaceholder( placeholder );
}
}
}
/**
* Removes a placeholder from this node.
* @param placeholder the placeholder to remove
* @return <code>true</code> if the placeholder was removed
*/
public boolean removePlaceholder( Path placeholder ){
if( placeholders != null ){
return placeholders.remove( placeholder );
}
return false;
}
/**
* Removes all placeholders in <code>placeholders</code> from this node
* @param placeholders the placeholders to remove
*/
public void removePlaceholders( Set<Path> placeholders ){
if( this.placeholders != null ){
this.placeholders.removeAll( placeholders );
}
}
/**
* Sets information about the placeholders of a {@link DockStation} that was
* child of this node.
* @param placeholderMap the placeholder information, can be <code>null</code>
*/
public void setPlaceholderMap( PlaceholderMap placeholderMap ){
if( this.placeholderMap != null ){
this.placeholderMap.setPlaceholderStrategy( null );
}
this.placeholderMap = placeholderMap;
if( this.placeholderMap != null ){
this.placeholderMap.setPlaceholderStrategy( getAccess().getOwner().getPlaceholderStrategy() );
getAccess().getPlaceholderSet().removeDoublePlaceholders( this, placeholderMap );
}
}
/**
* Moves the current {@link PlaceholderMap} to <code>destination</code>, overriding
* its old value. The map of this node is set to <code>null</code>
* @param destination the destination of the map
*/
public void movePlaceholderMap( SplitNode destination ){
destination.setPlaceholderMap( null );
destination.placeholderMap = placeholderMap;
this.placeholderMap = null;
}
/**
* Gets placeholder information of a child {@link DockStation}.
* @return the placeholder information, can be <code>null</code>
*/
public PlaceholderMap getPlaceholderMap(){
return placeholderMap;
}
/**
* Tells whether this node still has any use or can safely be removed from the tree
* @return <code>true</code> if this node has to remain in the tree, <code>false</code>
* otherwise
*/
public abstract boolean isOfUse();
/**
* Replaces this node with <code>node</code>. Does nothing if this node has no parent.
* @param node the replacement, not <code>null</code>
*/
public void replace( SplitNode node ){
if( node == null )
throw new IllegalArgumentException( "node must not be null" );
SplitNode parent = getParent();
if( parent != null ){
int location = parent.getChildLocation( this );
parent.setChild( node, location );
}
}
/**
* Removes this node from its parent, if there is a parent. The subtree
* remains intact and no {@link Dockable}s are removed from the station.
* @param shrink whether this node should attempt to shrink the tree such
* that no holes are left after this node was deleted
*/
public void delete( boolean shrink ){
PlaceholderMap map = getPlaceholderMap();
if( map != null ){
map.setPlaceholderStrategy( null );
}
SplitNode parent = getParent();
if( parent != null ){
if( shrink ){
if( parent instanceof Root ){
((Root)parent).setChild( null );
}
else{
if( !parent.hasPlaceholders() ){
Node node = (Node)parent;
SplitNode other = node.getLeft() == this ? node.getRight() : node.getLeft();
parent = node.getParent();
if( parent != null ){
int location = parent.getChildLocation( node );
parent.setChild( other, location );
}
}
}
}
else{
int location = parent.getChildLocation( this );
parent.setChild( null, location );
}
}
}
/**
* Splits this node into two nodes, a new parent {@link Node} is created and inserted.
* @param property description of a path in the tree
* @param depth the element of <code>property</code> which decides how to split this node
* @param newChild the new neighbor of this node, its location is described by <code>property</code>
*/
protected void split( SplitDockPathProperty property, int depth, SplitNode newChild ){
split( property, depth, newChild, -1 );
}
/**
* Splits this node into two nodes, a new parent {@link Node} is created and inserted.
* @param property description of a path in the tree
* @param depth the element of <code>property</code> which decides how to split this node
* @param newChild the new neighbor of this node, its location is described by <code>property</code>
* @param newNodeId the identifier of the new parent node, can be <code>-1</code>
*/
protected void split( SplitDockPathProperty property, int depth, SplitNode newChild, long newNodeId ){
Node split;
SplitDockPathProperty.Node node = property.getNode( depth );
SplitDockStation.Orientation orientation;
if( node.getLocation() == SplitDockPathProperty.Location.LEFT ||
node.getLocation() == SplitDockPathProperty.Location.RIGHT )
orientation = SplitDockStation.Orientation.HORIZONTAL;
else
orientation = SplitDockStation.Orientation.VERTICAL;
boolean reverse = node.getLocation() == SplitDockPathProperty.Location.RIGHT ||
node.getLocation() == SplitDockPathProperty.Location.BOTTOM;
SplitDockPathProperty.Node lastNode = property.getLastNode();
if( lastNode != null ){
newNodeId = lastNode.getId();
}
SplitNode parent = getParent();
int location = parent.getChildLocation( this );
if( reverse ){
split = createNode( newNodeId );
split.setOrientation( orientation );
split.setLeft( this );
split.setRight( newChild );
split.setDivider( 1 - node.getSize() );
}
else{
split = createNode( newNodeId );
split.setLeft( newChild );
split.setRight( this );
split.setOrientation( orientation );
split.setDivider( node.getSize() );
}
parent.setChild( split, location );
}
/**
* Creates a new {@link Leaf}
* @param id the unique identifier of the new leaf, can be -1
* @return the new leaf
*/
public Leaf createLeaf( long id ){
return access.createLeaf( id );
}
/**
* Creates a new {@link Node}.
* @param id the unique identifier of the new node, can be -1
* @return the new node
*/
public Node createNode( long id ){
return access.createNode( id );
}
/**
* Creates a new {@link Placeholder} calling {@link SplitDockAccess#createPlaceholder(long)}
* @param id the unique identifier of the new leaf, can be -1
* @return the new leaf
*/
public Placeholder createPlaceholder( long id ){
return access.createPlaceholder( id );
}
/**
* Gets the relative x-coordinate of this node on the owner-station. The coordinates
* are measured as fraction of the size of the owner-station.
* @return A value between 0 and 1
*/
public double getX() {
return x;
}
/**
* Gets the relative y-coordinate of this node on the owner-station. The coordinates
* are measured as fraction of the size of the owner-station.
* @return A value between 0 and 1
*/
public double getY() {
return y;
}
/**
* Gets the relative width of this node in relation to the owner-station.
* @return a value between 0 and 1
*/
public double getWidth() {
return width;
}
/**
* Gets the relative height of this node in relation to the owner-station.
* @return a value between 0 and 1
*/
public double getHeight() {
return height;
}
/**
* Sets the parent of this node.
* @param parent the new parent, can be <code>null</code>
*/
public void setParent( SplitNode parent ){
if( this.parent != null ){
SplitNode node = this.parent;
this.parent = null;
node.setChild( null, node.getChildLocation( this ) );
}
this.parent = parent;
}
/**
* Gets the parent of this node.
* @return the parent, can be <code>null</code>
*/
public SplitNode getParent(){
return parent;
}
/**
* Gets the (hopefully) unique id of this node.
* @return the unique id
*/
public long getId(){
return id;
}
/**
* Schedules a call to {@link #ensureIdUnique()} of the {@link Root} node. If this method is not called within the EDT,
* then the id is checked immediately. Several calls to this method may be merged into one invocation of
* {@link #ensureIdUnique()} for optimization. If there is no {@link Root} available, nothing happens.
*/
protected void ensureIdUniqueAsync(){
if( idChecked ){
idChecked = false;
if( EventQueue.isDispatchThread() ){
EventQueue.invokeLater( new Runnable(){
public void run(){
if( !idChecked ){
idChecked = true;
Root root = getRoot();
if( root != null ){
root.ensureIdUnique();
}
}
}
});
}
else{
idChecked = true;
Root root = getRoot();
if( root != null ){
root.ensureIdUnique();
}
}
}
}
/**
* Recursively visits all children of this {@link SplitNode} and ensures that no
* node has the same unique id. May change the id of some nodes if necessary.<br>
* After this method has completed, no two nodes in the subtree share the same id.
*/
protected void ensureIdUnique(){
long[] ids = new long[ getTotalChildrenCount() + 1 ];
ensureIdUnique( ids, 0 );
}
private int ensureIdUnique( long[] ids, int offset ){
idChecked = true;
int delta = 0;
ids[offset] = getId();
offset++;
for( int i = 0, n = getMaxChildrenCount(); i<n; i++ ){
SplitNode child = getChild( i );
if( child != null ){
delta += child.ensureIdUnique( ids, offset + delta );
}
}
boolean issue = true;
while( issue ){
issue = false;
long id = getId();
for( int i = 0; i < delta; i++ ){
if( ids[offset+i] == id ){
this.id = access.uniqueID();
issue = true;
break;
}
}
}
return delta + 1;
}
/**
* Counts the total number of children of this node, the total number of children is the total
* number of nodes and leafs in the tree below this node, excluding this node.
* @return the total number of children, can be 0
*/
public int getTotalChildrenCount(){
int max = getMaxChildrenCount();
int sum = 0;
for( int i = 0; i < max; i++ ){
SplitNode node = getChild( i );
if( node != null ){
sum += 1 + node.getTotalChildrenCount();
}
}
return sum;
}
/**
* Gets access to the owner-station
* @return the access
*/
protected SplitDockAccess getAccess(){
return access;
}
/**
* Tells whether this node (or one of this children) contains element that
* are visible to the user.
* @return <code>true</code> if this node or one of its children contains
* a graphical element
*/
public abstract boolean isVisible();
/**
* Gets the root of a subtree such that the root is visible and such that the
* is the uppermost visible node.
* @return the visible root, can be <code>null</code>, <code>this</code> or any
* child of this node
*/
public abstract SplitNode getVisible();
/**
* Gets the minimal size of this node.
* @return the minimal size in pixel
*/
public abstract Dimension getMinimumSize();
/**
* Gets the preferred size of this node.
* @return the preferred size in pixel
*/
public abstract Dimension getPreferredSize();
/**
* Updates the bounds of this node. If the node represents a {@link Component}, then
* the bounds of the component have to be updated as well.<br>
* This method is recursive, it will call {@link #updateBounds(double, double, double, double, double, double, boolean) updateBounds} on
* the children of this node.
* @param x the relative x-coordinate
* @param y the relative y-coordinate
* @param width the relative width of the node
* @param height the relative height of the node
* @param factorW a factor to be multiplied with <code>x</code> and <code>width</code>
* to get the size of the node in pixel
* @param factorH a factor to be multiplied with <code>y</code> and <code>height</code>
* to get the size of the node in pixel
* @param updateComponentBounds whether to update the bounds of {@link Component}s
* that are in the tree. If set to <code>false</code>, then all updates stay within
* the tree and the graphical user interface is not changed. That can be useful
* if more than one round of updates is necessary. If in doubt, set this parameter
* to <code>true</code>.
* @see #setBounds(double, double, double, double, double, double, boolean)
*/
public void updateBounds( double x, double y, double width, double height, double factorW, double factorH, boolean updateComponentBounds ){
setBounds( x, y, width, height, factorW, factorH, updateComponentBounds );
}
/**
* Updates the bounds of this node. If the node represents a {@link Component}, then
* the bounds of the component have to be updated as well. This method is <b>not</b> recursive, it does not
* call {@link #setBounds(double, double, double, double, double, double, boolean) getBounds} on the children of this node.
* @param x the relative x-coordinate
* @param y the relative y-coordinate
* @param width the relative width of the node
* @param height the relative height of the node
* @param factorW a factor to be multiplied with <code>x</code> and <code>width</code>
* to get the size of the node in pixel
* @param factorH a factor to be multiplied with <code>y</code> and <code>height</code>
* to get the size of the node in pixel
* @param updateComponentBounds whether to update the bounds of {@link Component}s
* that are in the tree. If set to <code>false</code>, then all updates stay within
* the tree and the graphical user interface is not changed. That can be useful
* if more than one round of updates is necessary. If in doubt, set this parameter
* to <code>true</code>.
* @see #updateBounds(double, double, double, double, double, double, boolean)
*/
public void setBounds( double x, double y, double width, double height, double factorW, double factorH, boolean updateComponentBounds ){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Gets the root of the tree in which this node is
* @return the root or <code>null</code>
*/
public Root getRoot(){
if( parent == null )
return null;
return parent.getRoot();
}
/**
* Determines where to drop the {@link Dockable} <code>drop</code>
* if the mouse is at location x/y.
* @param x the x-coordinate of the mouse
* @param y the y-coordinate of the mouse
* @param factorW a factor to be multiplied with the relative
* {@link #getX() x} and {@link #getWidth() width} to get the
* size in pixel.
* @param factorH a factor to be multiplied with the relative
* {@link #getY() y} and {@link #getHeight() height} to get the
* size in pixel.
* @param drop the {@link Dockable} which will be dropped
* @return where to drop the dockable or <code>null</code> if
* the dockable can't be dropped
*/
public abstract PutInfo getPut( int x, int y, double factorW, double factorH, Dockable drop );
/**
* Tells whether the coordinates x/y lie inside the override-zone of
* the {@link SplitDockStation} or not.
* @param x the x-coordinate of the mouse
* @param y the y-coordinate of the mouse
* @param factorW a factor to be multiplied with the relative
* {@link #getX() x} and {@link #getWidth() width} to get the
* size in pixel.
* @param factorH a factor to be multiplied with the relative
* {@link #getY() y} and {@link #getHeight() height} to get the
* size in pixel.
* @return <code>true</code> if the station should not allow child-stations
* to make a drop when the mouse is at x/y
*/
public abstract boolean isInOverrideZone( int x, int y, double factorW, double factorH );
/**
* Gets the leaf which represents <code>dockable</code>.
* @param dockable the Dockable whose leaf is searched
* @return the leaf or <code>null</code> if no leaf was found
*/
public abstract Leaf getLeaf( Dockable dockable );
/**
* Gets the Node whose divider area contains the point x/y. Only searches
* in the subtree with this node as root.
* @param x the x-coordinate
* @param y the y-coordinate
* @return the Node containing the point, if no Node was found,
* <code>null</code> is returned
*/
public abstract Node getDividerNode( int x, int y );
/**
* Gets the location of a child.
* @param child a child of this node
* @return the location of <code>child</code> or -1 if the child is unknown
*/
public abstract int getChildLocation( SplitNode child );
/**
* Adds a child to this node at a given location.
* @param child the new child
* @param location the location of the child
*/
public abstract void setChild( SplitNode child, int location );
/**
* Gets the maximal number of children this node can have.
* @return the maximal number of children
*/
public abstract int getMaxChildrenCount();
/**
* Gets the child at <code>location</code>.
* @param location the location of the child
* @return the child or <code>null</code> if the location is invalid or if there is no child at the location
*/
public abstract SplitNode getChild( int location );
/**
* Invokes one of the methods of the <code>visitor</code> for every
* child in the subtree with this as root.
* @param visitor the visitor
*/
public abstract void visit( SplitNodeVisitor visitor );
/**
* Creates or replaces children according to the values found in
* <code>key</code>. Note that this method does not remove any {@link Dockable}s
* from the station. They must be removed explicitly using {@link Leaf#setDockable(Dockable, bibliothek.gui.dock.DockHierarchyLock.Token)}
* @param key the key to read
* @param linksToSet a map that is to be filled with all new {@link Leaf}s and their {@link Dockable}s which are not yet set.
* @param checkValidity whether to ensure that all new {@link Dockable}s are
* acceptable or not.
*/
public abstract void evolve( SplitDockTree<Dockable>.Key key, boolean checkValidity, Map<Leaf, Dockable> linksToSet );
/**
* If there are elements left in <code>property</code>, then the next node
* is to be read and the <code>insert</code>-method of the matching child
* to be called.<br>
* If there are no children, then <code>dockable</code> has to be inserted
* as new child.<br>
* Otherwise this element is to be replaced by a node containing
* <code>this</code> and the a leaf with <code>dockable</code>.<br>
* Subclasses may wary this scheme in order to optimize or to find a better
* place for the <code>dockable</code>.
* @param property a list of nodes
* @param depth the index of the node that corresponds to this
* @param dockable the element to insert
* @return <code>true</code> if the element was inserted, <code>false</code>
* otherwise
*/
public abstract boolean insert( SplitDockPathProperty property, int depth, Dockable dockable );
/**
* Recursively searches for a node or leaf that uses the placeholder specified by
* <code>property</code> and inserts the <code>dockable</code> there. Also removes
* the placeholder from this node.
* @param property the placeholder to search
* @param dockable the new element
* @return <code>true</code> if the element was inserted, <code>false</code>
* otherwise
*/
public abstract boolean insert( SplitDockPlaceholderProperty property, Dockable dockable );
/**
* Inserts a new placeholder at this node.
* @param request more information about the request, including the placeholder to add
* @return <code>true</code> if the placeholder was added, <code>false</code> if it could
* not be added
*/
public abstract boolean aside( AsideRequest request );
/**
* Inserts a new placeholder at location <code>property</code>.
* @param property the path to the placeholder
* @param index the current segment, represents <code>this</code> node
* @param request more information about the request, including the placeholder to add
* @return <code>true</code> if the placeholder was added, <code>false</code> if it could
* not be added
*/
public abstract boolean aside( SplitDockPathProperty property, int index, AsideRequest request );
/**
* Searches and returns the first {@link SplitNode} which contains <code>placeholder</code>.
* @param placeholder the placeholder to search
* @return the node containing <code>placeholder</code> or <code>null</code>
*/
public SplitNode getPlaceholderNode( Path placeholder ){
if( hasPlaceholder( placeholder )){
return this;
}
for( int i = 0, n = getMaxChildrenCount(); i<n; i++ ){
SplitNode child = getChild( i );
if( child != null ){
SplitNode result = child.getPlaceholderNode( placeholder );
if( result != null ){
return result;
}
}
}
return null;
}
/**
* Writes the contents of this node into a new tree create by <code>factory</code>.
* @param <N> the type of element the <code>factory</code> will create
* @param factory the factory transforming the elements of the tree into a
* new form.
* @return the representation of this node
*/
public abstract <N> N submit( SplitTreeFactory<N> factory );
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
toString( 0, builder );
return builder.toString();
}
/**
* Writes some contents of this node into <code>out</code>.
* @param tabs the number of tabs that should be added before the text if
* a new line is necessary.
* @param out the container to write into
*/
public abstract void toString( int tabs, StringBuilder out );
/**
* Gets the size of this node in pixel.
* @return the size of the node
*/
public Dimension getSize(){
Root root = getRoot();
double fw = root.getWidthFactor();
double fh = root.getHeightFactor();
return new Dimension(
(int)(width * fw + 0.5),
(int)(height * fh + 0.5 ));
}
/**
* Gets the size and location of this node in pixel where the point
* 0/0 is equal to the point 0/0 on the owner-station. This method calculates
* these values anew, clients interested in the current bounds should
* use {@link VisibleSplitNode#getCurrentBounds()}.
* @return the size and location
*/
public Rectangle getBounds(){
Root root = getRoot();
double fw = root.getWidthFactor();
double fh = root.getHeightFactor();
Rectangle rec = new Rectangle(
(int)(x * fw + 0.5),
(int)(y * fh + 0.5),
(int)(width * fw + 0.5),
(int)(height * fh + 0.5 ));
Rectangle base = root.getBaseBounds();
rec.x = Math.min( base.width, Math.max( base.x, rec.x ));
rec.y = Math.min( base.height, Math.max( base.y, rec.y ));
rec.width = Math.min( base.width - rec.x + base.x, Math.max( 0, rec.width ));
rec.height = Math.min( base.height - rec.y + base.y, Math.max( 0, rec.height ));
return rec;
}
/**
* Creates a leaf for <code>dockable</code>. This method only
* creates the leaf, but does not connect leaf and <code>dockable</code>.
* @param dockable the element to put into a leaf
* @param id the unique identifier of the new leaf, can be -1
* @return the new leaf or <code>null</code> if the leaf would not be valid
*/
protected Leaf create( Dockable dockable, long id ){
SplitDockStation split = access.getOwner();
DockController controller = split.getController();
DockAcceptance acceptance = controller == null ? null : controller.getAcceptance();
if( !dockable.accept( split ) || !split.accept( dockable ))
return null;
if( acceptance != null ){
if( !acceptance.accept( split, dockable ))
return null;
}
Leaf leaf = createLeaf( id );
return leaf;
}
/**
* Creates a new node using the contents of <code>key</code>.
* @param key the key to read
* @param checkValidity whether to ensure that all new {@link Dockable}s
* are acceptable or not.
* @param linksToSet a map that will be filled up with new {@link Leaf}s whose {@link Dockable}s have not yet been set
* @return the new node
*/
protected SplitNode create( SplitDockTree<Dockable>.Key key, boolean checkValidity, Map<Leaf, Dockable> linksToSet ){
SplitDockTree<Dockable> tree = key.getTree();
if( tree.isDockable( key )){
Dockable[] dockables = tree.getDockables( key );
if( dockables == null || dockables.length == 0 ){
Path[] placeholders = tree.getPlaceholders( key );
Placeholder leaf = createPlaceholder( key.getNodeId() );
leaf.setPlaceholders( placeholders );
leaf.setPlaceholderMap( tree.getPlaceholderMap( key ));
return leaf;
}
else{
SplitDockStation split = access.getOwner();
DockController controller = split.getController();
DockAcceptance acceptance = controller == null ? null : controller.getAcceptance();
Leaf leaf;
boolean removePlaceholderMap = false;
if( dockables.length == 1 ){
if( checkValidity ){
if( !dockables[0].accept( split ) ||
!split.accept( dockables[0] ))
throw new SplitDropTreeException( split, "No acceptance for " + dockables[0] );
if( acceptance != null ){
if( !acceptance.accept( split, dockables[0] ))
throw new SplitDropTreeException( split, "DockAcceptance does not allow child " + dockables[0] );
}
}
leaf = createLeaf( key.getNodeId() );
linksToSet.put( leaf, dockables[0] );
}
else{
if( checkValidity ){
if( !dockables[0].accept( split, dockables[1] ) ||
!dockables[1].accept( split, dockables[1] ))
throw new SplitDropTreeException( split,
"No acceptance for combination of " + dockables[0] + " and " + dockables[1] );
if( acceptance != null ){
if( !acceptance.accept( split, dockables[0], dockables[1] ))
throw new SplitDropTreeException( split,
"DockAcceptance does not allow to combine " + dockables[0] + " and " + dockables[1] );
}
}
Combiner combiner = access.getOwner().getCombiner();
CombinerSource source = new NodeCombinerSource( dockables[0], dockables[1], key.getTree().getPlaceholderMap( key ) );
CombinerTarget target = combiner.prepare( source, Enforcement.HARD );
Dockable combination = combiner.combine( source, target );
removePlaceholderMap = true;
if( dockables.length == 2 ){
leaf = createLeaf( key.getNodeId() );
linksToSet.put( leaf, combination );
DockStation station = combination.asDockStation();
if( station != null ){
Dockable selected = key.getTree().getSelected( key );
if( selected != null )
station.setFrontDockable( selected );
}
}
else{
DockStation station = combination.asDockStation();
if( station == null )
throw new SplitDropTreeException( access.getOwner(), "Combination of two Dockables does not create a new station" );
leaf = createLeaf( key.getNodeId() );
linksToSet.put( leaf, combination );
for( int i = 2; i < dockables.length; i++ ){
Dockable dockable = dockables[ i ];
if( checkValidity ){
if( !dockable.accept( station ) || !station.accept( dockable ))
throw new SplitDropTreeException( access.getOwner(), "No acceptance of " + dockable + " and " + station );
if( acceptance != null ){
if( !acceptance.accept( station, dockable ))
throw new SplitDropTreeException( split,
"DockAcceptance does not allow " + dockable + " as child of " + station );
}
}
station.drop( dockable );
}
Dockable selected = key.getTree().getSelected( key );
if( selected != null )
station.setFrontDockable( selected );
}
}
leaf.evolve( key, checkValidity, linksToSet );
if( removePlaceholderMap ){
leaf.setPlaceholderMap( null );
}
return leaf;
}
}
else{
Node node = createNode( key.getNodeId() );
node.evolve( key, checkValidity, linksToSet );
return node;
}
}
/**
* Calculates how much of the rectangle given by the property lies inside
* this node and how much of this node lies in the rectangle. The result
* is a value between 0 and 1 which is 1 only if this node and the rectangle
* are identical. The result is 0 if they do not have a shared area.
* @param property the property that gives a rectangle
* @return Area of intersection divided by the maxima of the area
* of the rectangle and of this node.
*/
public double intersection( SplitDockProperty property ){
double rx1 = Math.max( x, property.getX() );
double ry1 = Math.max( y, property.getY() );
double rx2 = Math.min( x+width, property.getX() + property.getWidth() );
double ry2 = Math.min( y+height, property.getY() + property.getHeight() );
if( rx1 > rx2 || ry1 > ry2 )
return 0;
if( property.getWidth() == 0 || property.getHeight() == 0 )
return 0;
double max = Math.max( property.getWidth()*property.getHeight(), width*height );
return (rx2-rx1)*(ry2-ry1) / max;
}
/**
* Calculates on which side of the node the point <code>kx/ky</code> lies.
* @param kx the relative x-coordinate of the point
* @param ky the relative y-coordinate of the point
* @return One side of the node
*/
public PutInfo.Put relativeSidePut( double kx, double ky ){
if( above( x, y, x+width, y+height, kx, ky )){
if( above( x, y+height, x+width, y, kx, ky ))
return PutInfo.Put.TOP;
else
return PutInfo.Put.RIGHT;
}
else{
if( above( x, y+height, x+width, y, kx, ky ))
return PutInfo.Put.LEFT;
else
return PutInfo.Put.BOTTOM;
}
}
/**
* Calculates whether the point <code>x/y</code> lies above
* the line going through <code>x1/y1</code> and <code>x2/y2</code>.
* @param x1 the x-coordinate of the first point on the line
* @param y1 the y-coordinate of the first point on the line
* @param x2 the x-coordinate of the second point on the line
* @param y2 the y-coordinate of the second point on the line
* @param x the x-coordinate of the point which may be above the line
* @param y the y-coordinate of the point which may be above the line
* @return <code>true</code> if the point lies above the line, <code>false</code>
* otherwise
*/
public static boolean above( double x1, double y1, double x2, double y2, double x, double y ){
double a = y1 - y2;
double b = x2 - x1;
if( b == 0 )
return false;
double c = a*x1 + b*y1;
double sy = (c - a*x) / b;
return y < sy;
}
/**
* Simple {@link CombinerSource} used for creating new {@link DockStation}s during creation of a {@link SplitNode}.
* @author Benjamin Sigg
*/
private class NodeCombinerSource implements CombinerSource{
private Dockable child;
private Dockable dropping;
private PlaceholderMap placeholders;
/**
* Creates a new source.
* @param child the old dockable
* @param dropping the new dockable
* @param placeholders placeholders associated with this location
*/
public NodeCombinerSource( Dockable child, Dockable dropping, PlaceholderMap placeholders ){
this.child = child;
this.dropping = dropping;
this.placeholders = placeholders;
}
public Point getMousePosition(){
return null;
}
public Dockable getNew(){
return dropping;
}
public Dockable getOld(){
return child;
}
public DockableDisplayer getOldDisplayer(){
return null;
}
public DockStation getParent(){
return access.getOwner();
}
public PlaceholderMap getPlaceholders(){
return placeholders;
}
public Dimension getSize(){
return null;
}
public boolean isMouseOverTitle(){
return true;
}
}
}