/*
* 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.Dimension;
import java.awt.Rectangle;
import java.util.Map;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.SplitDockStation.Orientation;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.station.split.SplitDockPathProperty.Location;
import bibliothek.util.Path;
/**
* A Node represents an element in the tree of a {@link SplitDockStation}.
* Every node has two children. The areas of the children are separated by
* a "divider", whose position can be changed.
* @author Benjamin Sigg
*/
public class Node extends VisibleSplitNode implements Divideable{
/** The area of the left child is either at the left or at the top of the area of this node. */
private SplitNode left;
/** The area of the right child is either at the right or at the bottom of the area of this node. */
private SplitNode right;
/** The location of the divider. It's the fraction of the area of the node which is given to the left child. */
private double divider = 0.5;
/** The order in which the children are aligned. */
private Orientation orientation = Orientation.VERTICAL;
/** The area of the divider between the two children */
private Rectangle dividerBounds = new Rectangle();
/** whether this node is visible or not */
private boolean visible;
/** whether {@link #visible} has any meaning */
private boolean visibleCached = false;
/**
* Constructs a new node.
* @param access the access to the owner-station of this node.
* @param left the left child
* @param right the right child
* @param orientation how the children are aligned
*/
public Node( SplitDockAccess access, SplitNode left, SplitNode right, Orientation orientation ){
this( access, left, right );
this.orientation = orientation;
}
/**
* Constructs a new node.
* @param access the access to the owner-station of this node.
* @param left the left child
* @param right the right child
* @param orientation how the children are aligned
* @param id the unique id of this node, can be -1
*/
public Node( SplitDockAccess access, SplitNode left, SplitNode right, Orientation orientation, long id ){
this( access, left, right, id );
this.orientation = orientation;
}
/**
* Constructs a new node.
* @param access the access to the owner-station of this node
* @param left the left child
* @param right the right child
*/
public Node( SplitDockAccess access, SplitNode left, SplitNode right ){
super( access, -1 );
setRight( right );
setLeft( left );
}
/**
* Constructs a new node.
* @param access the access to the owner-station of this node
* @param left the left child
* @param right the right child
* @param id the unique id of this node
*/
public Node( SplitDockAccess access, SplitNode left, SplitNode right, long id ){
super( access, id );
setRight( right );
setLeft( left );
}
/**
* Constructs a new node.
* @param access the access to the owner-station of this node
*/
public Node( SplitDockAccess access ){
super( access, -1 );
}
/**
* Constructs a new node.
* @param access the access to the owner-station of this node
* @param id the unique id of this node
*/
public Node( SplitDockAccess access, long id ){
super( access, id );
}
/**
* Sets the left child of this node. The area of this child
* will be in the left or the upper half of the area of this node.<br>
* Note that setting the child to <code>null</code> does not delete
* the child from the system, only a call to {@link SplitNode#delete(boolean)}
* does that.
* @param left the left child or <code>null</code>
*/
public void setLeft( SplitNode left ){
if( this.left != null )
this.left.setParent( null );
this.left = left;
clearVisibility();
if( left != null ){
left.setParent( this );
}
treeChanged();
if( left != null ){
ensureIdUniqueAsync();
}
getAccess().getOwner().revalidate();
getAccess().getOwner().repaint();
getAccess().repositioned( this );
}
/**
* Gets the left child of this node.
* @return the child
* @see #setLeft(SplitNode)
*/
public SplitNode getLeft() {
return left;
}
/**
* Sets the right child of this node. The area of this child
* will be in the right or the bottom half of the area of this
* node.<br>
* Note that setting the child to <code>null</code> does not delete
* the child from the system, only a call to {@link SplitNode#delete(boolean)}
* does that.
* @param right the right child
*/
public void setRight( SplitNode right ){
if( this.right != null )
this.right.setParent( null );
this.right = right;
clearVisibility();
if( right != null ){
right.setParent( this );
}
treeChanged();
if( right != null ){
ensureIdUniqueAsync();
}
getAccess().getOwner().revalidate();
getAccess().getOwner().repaint();
getAccess().repositioned( this );
}
/**
* Gets the right child of this node.
* @return the child
* @see #setRight(SplitNode)
*/
public SplitNode getRight() {
return right;
}
@Override
public int getChildLocation( SplitNode child ) {
if( left == child )
return 0;
if( right == child )
return 1;
return -1;
}
@Override
public void setChild( SplitNode child, int location ) {
if( location == 0 )
setLeft( child );
else if( location == 1 )
setRight( child );
else
throw new IllegalArgumentException( "Location not valid " + location );
}
@Override
public int getMaxChildrenCount(){
return 2;
}
@Override
public SplitNode getChild( int location ){
switch( location ){
case 0: return getLeft();
case 1: return getRight();
default: return null;
}
}
public Orientation getOrientation() {
return orientation;
}
/**
* Changes the orientation of this node.
* @param orientation the new orientation
*/
public void setOrientation( Orientation orientation ) {
if( orientation == null )
throw new NullPointerException( "orientation must not be null" );
this.orientation = orientation;
getAccess().getOwner().revalidate();
}
@Override
public Dimension getMinimumSize() {
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
Dimension minLeft = leftVisible ? left.getMinimumSize() : null;
Dimension minRight = rightVisible ? right.getMinimumSize() : null;
return getSize( minLeft, minRight );
}
@Override
public Dimension getPreferredSize(){
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
Dimension minLeft = leftVisible ? left.getPreferredSize() : null;
Dimension minRight = rightVisible ? right.getPreferredSize() : null;
return getSize( minLeft, minRight );
}
private Dimension getSize( Dimension left, Dimension right ){
if( left != null && right != null ){
int divider = getAccess().getOwner().getDividerSize();
if( orientation == Orientation.HORIZONTAL ){
return new Dimension(
left.width + divider + right.width,
Math.max( left.height, right.height ));
}
else{
return new Dimension(
Math.max( left.width, right.width ),
left.height + divider + right.height );
}
}
else if( left != null ){
return left;
}
else if( right != null ){
return right;
}
else{
return new Dimension();
}
}
public void setDivider( double divider ){
if( this.divider != divider ){
this.divider = divider;
getAccess().getOwner().revalidate();
getAccess().getOwner().repaint();
getAccess().repositioned( this );
}
}
public double getDivider() {
return divider;
}
public double getActualDivider(){
return validateDivider( getDivider() );
}
public double validateDivider( double divider ){
return getStation().getCurrentSplitLayoutManager().validateDivider( getStation(), divider, this );
}
@Override
public boolean isVisible(){
if( !visibleCached ){
visible = left == null || right == null || (left.isVisible() || right.isVisible());
visibleCached = true;
}
return visible;
}
private void clearVisibility(){
visibleCached = false;
SplitNode parent = getParent();
if( parent instanceof Node ){
((Node)parent).clearVisibility();
}
}
@Override
public SplitNode getVisible(){
if( left != null && right != null ){
boolean leftVisible = left.isVisible();
boolean rightVisible = right.isVisible();
if( leftVisible && rightVisible ){
return this;
}
if( leftVisible ){
return left;
}
if( rightVisible ){
return right;
}
}
return null;
}
@Override
public boolean isOfUse(){
return true;
}
@Override
public void updateBounds( double x, double y, double width, double height, double factorW, double factorH, boolean components ) {
super.updateBounds( x, y, width, height, factorW, factorH, components );
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && !rightVisible ){
left.updateBounds( x, y, width, height, factorW, factorH, components );
}
else if( !leftVisible && rightVisible ){
right.updateBounds( x, y, width, height, factorW, factorH, components );
}
else if( leftVisible && rightVisible ){
double divider = getActualDivider();
int dividerSize = getAccess().getOwner().getDividerSize();
if( orientation == Orientation.HORIZONTAL ){
// Components are left and right
double dividerWidth = factorW > 0 ? Math.max( 0, dividerSize / factorW) : 0.0;
double dividerLocation = width * divider;
if( left != null )
left.updateBounds( x, y, dividerLocation - dividerWidth/2, height, factorW, factorH, components );
if( right != null )
right.updateBounds( x + dividerLocation + dividerWidth/2, y,
width - dividerLocation - dividerWidth/2, height, factorW, factorH, components );
}
else{
double dividerHeight = factorH > 0 ? Math.max( 0, dividerSize / factorH ) : 0.0;
double dividerLocation = height * divider;
if( left != null)
left.updateBounds( x, y, width, dividerLocation - dividerHeight / 2, factorW, factorH, components );
if( right != null)
right.updateBounds( x, y + dividerLocation + dividerHeight / 2,
width, height - dividerLocation - dividerHeight/2, factorW, factorH, components );
}
}
}
@Override
public void setBounds( double x, double y, double width, double height, double factorW, double factorH, boolean updateComponentBounds ){
super.setBounds( x, y, width, height, factorW, factorH, updateComponentBounds );
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible ){
double divider = getActualDivider();
int dividerSize = getAccess().getOwner().getDividerSize();
if( orientation == Orientation.HORIZONTAL ){
// Components are left and right
double dividerWidth = factorW > 0 ? Math.max( 0, dividerSize / factorW) : 0.0;
double dividerLocation = width * divider;
dividerBounds.setBounds(
(int)(( x+dividerLocation-dividerWidth/2 )*factorW ),
(int)( y*factorH ),
dividerSize,
(int)( height*factorH + 0.5 ));
}
else{
double dividerHeight = factorH > 0 ? Math.max( 0, dividerSize / factorH ) : 0.0;
double dividerLocation = height * divider;
dividerBounds.setBounds(
(int)(x*factorW),
(int)((y+dividerLocation-dividerHeight/2)*factorH ),
(int)(width*factorW + 0.5),
dividerSize );
}
}
}
public Rectangle getDividerBounds( double divider, Rectangle bounds ){
if( bounds == null )
bounds = new Rectangle();
Root root = getRoot();
double factorW = root.getWidthFactor();
double factorH = root.getHeightFactor();
int dividerSize = getAccess().getOwner().getDividerSize();
if( orientation == Orientation.HORIZONTAL ){
// Components are left and right
double dividerWidth = dividerSize / factorW;
double dividerLocation = width * divider;
bounds.setBounds(
(int)(( x+dividerLocation-dividerWidth/2 )*factorW + 0.5 ),
(int)( y*factorH + 0.5 ),
dividerSize,
(int)( height*factorH + 0.5 ));
}
else{
double dividerHeight = dividerSize / factorH;
double dividerLocation = height * divider;
bounds.setBounds(
(int)(x*factorW + 0.5),
(int)((y+dividerLocation-dividerHeight/2)*factorH + 0.5),
(int)(width*factorW + 0.5),
dividerSize );
}
return bounds;
}
public double getDividerAt( int x, int y ){
Root root = getRoot();
if( orientation == Orientation.HORIZONTAL ){
double mx = x / root.getWidthFactor();
return (mx - this.x) / width;
}
else{
double my = y / root.getHeightFactor();
return (my - this.y) / height;
}
}
@Override
public PutInfo getPut( int x, int y, double factorW, double factorH, Dockable drop ){
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible ){
if( orientation == Orientation.HORIZONTAL ){
if( x < (this.x + divider*width)*factorW ){
// left
return left == null ? null : left.getPut( x, y, factorW, factorH, drop );
}
else{
// right
return right == null ? null : right.getPut( x, y, factorW, factorH, drop );
}
}
else{
if( y < (this.y + divider*height)*factorH ){
// top
return left == null ? null : left.getPut( x, y, factorW, factorH, drop );
}
else{
// bottom
return right == null ? null : right.getPut( x, y, factorW, factorH, drop );
}
}
}
else if( leftVisible ){
return left.getPut( x, y, factorW, factorH, drop );
}
else if( rightVisible ){
return right.getPut( x, y, factorW, factorH, drop );
}
else{
return null;
}
}
@Override
public boolean isInOverrideZone( int x, int y, double factorW, double factorH ) {
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible ){
if( orientation == Orientation.HORIZONTAL ){
if( x < (this.x + divider*width)*factorW ){
// left
return left.isInOverrideZone( x, y, factorW, factorH );
}
else{
// right
return right.isInOverrideZone( x, y, factorW, factorH );
}
}
else{
if( y < (this.y + divider*height)*factorH ){
// top
return left.isInOverrideZone( x, y, factorW, factorH );
}
else{
// bottom
return right.isInOverrideZone( x, y, factorW, factorH );
}
}
}
else if( leftVisible ){
return left.isInOverrideZone( x, y, factorW, factorH );
}
else if( rightVisible ){
return right.isInOverrideZone( x, y, factorW, factorH );
}
else{
return false;
}
}
@Override
public void evolve( SplitDockTree<Dockable>.Key key, boolean checkValidity, Map<Leaf, Dockable> linksToSet ){
SplitDockTree<Dockable> tree = key.getTree();
setPlaceholders( tree.getPlaceholders( key ) );
setPlaceholderMap( tree.getPlaceholderMap( key ) );
if( tree.isHorizontal( key )){
orientation = SplitDockStation.Orientation.HORIZONTAL;
setLeft( create( tree.getLeft( key ), checkValidity, linksToSet ));
setRight( create( tree.getRight( key ), checkValidity, linksToSet ));
setDivider( tree.getDivider( key ));
}
else{
orientation = SplitDockStation.Orientation.VERTICAL;
setLeft( create( tree.getTop( key ), checkValidity, linksToSet ));
setRight( create( tree.getBottom( key ), checkValidity, linksToSet ));
setDivider( tree.getDivider( key ));
}
}
@Override
public boolean insert( SplitDockPlaceholderProperty property, Dockable dockable ){
Path placeholder = property.getPlaceholder();
if( hasPlaceholder( placeholder )){
// that is ... unexpected, must have been a client. Split this node and assign
// all remaining placeholders to a new leaf.
Leaf leaf = create( dockable, -1 );
if( leaf == null ){
return false;
}
getAccess().getPlaceholderSet().set( null, placeholder, this );
leaf.setPlaceholders( getPlaceholders() );
Node node = createNode( -1 );
node.setDivider( 0.5 );
if( width > height ){
node.setOrientation( Orientation.HORIZONTAL );
}
else{
node.setOrientation( Orientation.VERTICAL );
}
SplitNode parent = getParent();
int location = parent.getChildLocation( this );
node.setLeft( leaf );
node.setRight( this );
parent.setChild( node, location );
leaf.setDockable( dockable, null );
return true;
}
if( left != null && left.insert( property, dockable )){
return true;
}
if( right != null && right.insert( property, dockable )){
return true;
}
return false;
}
@Override
public boolean aside( AsideRequest request ){
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible ){
return left.aside( request );
}
else if( rightVisible ){
return right.aside( request );
}
else{
return false;
}
}
@Override
public boolean aside( SplitDockPathProperty property, int depth, AsideRequest request ){
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible ){
if( depth >= property.size() ){
return false;
}
else{
SplitDockPathProperty.Node node = property.getNode( depth );
if( needToExpand( node )){
if( request.getPlaceholder() != null ){
long placeholderId = getLeafId( property );
long splitId = getSplitId( property, depth );
Placeholder placeholder = createPlaceholder( splitId );
split( property, depth, placeholder, placeholderId );
placeholder.addPlaceholder( request.getPlaceholder() );
}
return true;
}
else{
if( isLeftOrTop( node.getLocation() )){
return left.aside( property, depth+1, request );
}
else{
return right.aside( property, depth+1, request );
}
}
}
}
else if( leftVisible ){
return left.aside( property, depth, request );
}
else if( rightVisible ){
return right.aside( property, depth, request );
}
else{
return false;
}
}
@Override
public boolean insert( SplitDockPathProperty property, int depth, Dockable dockable ) {
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible ){
if( depth >= property.size() ){
// there is no description where to put the element
// try using the theoretical boundaries of the element
return getAccess().drop( dockable, property.toLocation( this ), this );
}
else{
SplitDockPathProperty.Node node = property.getNode( depth );
if( needToExpand( node ) ){
// split up this node
long leafId = getLeafId( property );
long splitId = getSplitId( property, depth );
Leaf leaf = create( dockable, leafId );
if( leaf == null )
return false;
split( property, depth, leaf, splitId );
leaf.setDockable( dockable, null );
return true;
}
else{
// forward the call to a child
if( isLeftOrTop( node.getLocation() ) ){
return left.insert( property, depth+1, dockable );
}
else{
return right.insert( property, depth+1, dockable );
}
}
}
}
else if( leftVisible ){
return left.insert( property, depth, dockable );
}
else if( rightVisible ){
return right.insert( property, depth, dockable );
}
else{
return false;
}
}
private long getLeafId( SplitDockPathProperty property ){
long leafId = property.getLeafId();
SplitDockPathProperty.Node lastNode = null;
if( leafId == -1 ){
lastNode = property.getLastNode();
if( lastNode != null ){
leafId = lastNode.getId();
}
}
return leafId;
}
private long getSplitId( SplitDockPathProperty property, int depth ){
SplitDockPathProperty.Node node = property.getNode( depth );
SplitDockPathProperty.Node lastNode = null;
if( property.getLeafId() == -1 ){
lastNode = property.getLastNode();
}
if( lastNode != node ){
if( depth > 0 ){
return property.getNode( depth-1 ).getId();
}
else{
return node.getId();
}
}
return -1;
}
private boolean isLeftOrTop( Location location ){
return location == Location.LEFT || location == Location.TOP;
}
private boolean needToExpand( SplitDockPathProperty.Node node ){
boolean expand =
// if this node is horizontal, but the path is vertical
( orientation == SplitDockStation.Orientation.HORIZONTAL &&
(node.getLocation() == SplitDockPathProperty.Location.TOP ||
node.getLocation() == SplitDockPathProperty.Location.BOTTOM )) ||
// ... or if this node is vertical, but the path is horizontal
( orientation == SplitDockStation.Orientation.VERTICAL &&
(node.getLocation() == SplitDockPathProperty.Location.LEFT ||
node.getLocation() == SplitDockPathProperty.Location.RIGHT ));
if( node.getId() != -1 && node.getId() != getId() ){
expand = true;
}
return expand;
}
@Override
public <N> N submit( SplitTreeFactory<N> factory ) {
if( orientation == SplitDockStation.Orientation.HORIZONTAL )
return factory.horizontal( left.submit( factory ), right.submit( factory ), divider, getId(), getPlaceholders(), getPlaceholderMap(), isVisible() );
else
return factory.vertical( left.submit( factory ), right.submit( factory ), divider, getId(), getPlaceholders(), getPlaceholderMap(), isVisible() );
}
@Override
public Leaf getLeaf( Dockable dockable ) {
if( left != null && left.isVisible() ){
Leaf leaf = left.getLeaf( dockable );
if( leaf != null )
return leaf;
}
if( right != null && right.isVisible() )
return right.getLeaf( dockable );
else
return null;
}
@Override
public Node getDividerNode( int x, int y ){
boolean leftVisible = left == null || left.isVisible();
boolean rightVisible = right == null || right.isVisible();
if( leftVisible && rightVisible && dividerBounds.contains( x, y ))
return this;
if( left != null && leftVisible ){
Node node = left.getDividerNode( x, y );
if( node != null )
return node;
}
if( right != null && rightVisible ){
return right.getDividerNode( x, y );
}
else{
return null;
}
}
@Override
public void visit( SplitNodeVisitor visitor ) {
visitor.handleNode( this );
if( left != null ){
left.visit( visitor );
}
if( right != null ){
right.visit( visitor );
}
}
@Override
public void toString( int tabs, StringBuilder out ) {
out.append( "Node[ ");
out.append( orientation );
out.append( " , id=" );
out.append( getId() );
out.append( ", placeholders={" );
Path[] placeholders = getPlaceholders();
if( placeholders != null ){
for( int i = 0; i < placeholders.length; i++ ){
if( i > 0 ){
out.append( ", " );
}
out.append( placeholders[i] );
}
}
out.append( "}]" );
out.append( '\n' );
for( int i = 0; i < tabs+1; i++ )
out.append( '\t' );
if( left == null )
out.append( "<null>" );
else
left.toString( tabs+1, out );
out.append( '\n' );
for( int i = 0; i < tabs+1; i++ )
out.append( '\t' );
if( right == null )
out.append( "<null>" );
else
right.toString( tabs+1, out );
}
}