/*
* 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.Insets;
import java.awt.Rectangle;
import java.util.Map;
import javax.swing.JComponent;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.layout.location.AsideRequest;
/**
* The root of the tree that represents the internal structure of a
* {@link SplitDockStation}.
* @author Benjamin Sigg
*/
public class Root extends SpanSplitNode{
/** the single child of this root */
private SplitNode child;
/** tells whether the subtree has changed since the last reset */
private boolean treeChanged = true;
/** The result of {@link #getBaseBounds()} */
private Rectangle baseBounds = null;
/**
* Creates a new root.
* @param access the access to internal methods of the
* {@link SplitDockStation}, must not be <code>null</code>
*/
public Root( SplitDockAccess access ){
this( access, -1 );
}
/**
* Creates a new root.
* @param access the access to internal methods of the
* {@link SplitDockStation}, must not be <code>null</code>
* @param id the unique identifier of this root
*/
public Root( SplitDockAccess access, long id ){
super( access, id );
}
@Override
protected void treeChanged(){
treeChanged = true;
}
/**
* Tells whether the tree below this root has changed (children have
* been added or removed) since the boundaries of this root were
* last updated.
* @return <code>true</code> if the tree changed
*/
public boolean hasTreeChanged(){
return treeChanged;
}
/**
* Sets the child of this root. Every root has only one child.<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 child the child of the root, can be <code>null</code>
*/
public void setChild( SplitNode child ){
if( this.child != child ){
if( this.child != null )
this.child.setParent( null );
this.child = child;
if( child != null ){
child.delete( false );
child.setParent( this );
}
treeChanged();
if( child != null ){
ensureIdUniqueAsync();
}
getAccess().getOwner().revalidate();
getAccess().getOwner().repaint();
getAccess().repositioned( this );
}
}
/**
* Gets the child of this root.
* @return the child or <code>null</code>
* @see #setChild(SplitNode)
*/
public SplitNode getChild() {
return child;
}
@Override
public int getChildLocation( SplitNode child ) {
if( child == this.child )
return 0;
return -1;
}
@Override
public void setChild( SplitNode child, int location ) {
if( location == 0 )
setChild( child );
else
throw new IllegalArgumentException( "Location invalid: " + location );
}
@Override
public int getMaxChildrenCount(){
return 1;
}
@Override
public SplitNode getChild( int location ){
if( location == 0 ){
return getChild();
}
return null;
}
@Override
public Root getRoot() {
return this;
}
@Override
public boolean isOfUse(){
return true;
}
@Override
public Dimension getMinimumSize() {
Dimension result = child == null ? null : child.getMinimumSize();
if( result == null )
return new Dimension( 0, 0 );
return result;
}
@Override
public Dimension getPreferredSize(){
Dimension result = child == null ? null : child.getPreferredSize();
if( result == null )
return new Dimension( 0, 0 );
return result;
}
/**
* Gets the factor which has to be multiplied with relative x coordinates
* and widths to get their size in pixel.
* @return the horizontal stretch factor
*/
public double getWidthFactor(){
return getBaseBounds().getWidth();
}
/**
* Gets the factor which has to be multiplied with a relative y coordinate
* or height to get their size in pixel.
* @return the vertical stretch factor
*/
public double getHeightFactor(){
return getBaseBounds().getHeight();
}
/**
* Sets the result of {@link #getBaseBounds()}, a value of <code>null</code> allows
* this {@link Root} to calculate the base bounds anew.
* @param baseBounds the result of {@link #setBaseBounds(Rectangle)} or <code>null</code>
* if the boundaries should be calculated automatically
*/
public void setBaseBounds( Rectangle baseBounds ){
this.baseBounds = baseBounds;
}
/**
* Gets the location and size of the area which can be occupied by the children.
* @return the boundaries of the base {@link Component}
* @see #setBaseBounds(Rectangle)
*/
public Rectangle getBaseBounds(){
if( baseBounds != null ){
return baseBounds;
}
JComponent base = getAccess().getOwner().getBasePane();
Insets insets = base.getInsets();
int x = 0;
int y = 0;
int width = base.getWidth();
int height = base.getHeight();
if( insets != null ){
x = insets.left;
y = insets.top;
width -= insets.left + insets.right;
height -= insets.top + insets.bottom;
}
return new Rectangle( x, y, width, height );
}
/**
* Gets the preferred operation when dragging the {@link Dockable}
* <code>drop</code> to the location <code>x/y</code>.
* @param x the x-coordinate of the mouse
* @param y the y-coordinate of the mouse
* @param drop the Dockable which will be dropped
* @return where to trop the Dockable or <code>null</code>
*/
public PutInfo getPut( int x, int y, Dockable drop ){
double factorW = getWidthFactor();
double factorH = getHeightFactor();
return getPut( x, y, factorW, factorH, drop );
}
/**
* Tells whether the location x/y is in the override zone.
* @param x the x-coordinate of the mouse
* @param y the y-coordinate of the mouse
* @return <code>true</code> if this station should have priority
* over all other stations when the mouse is in x/y.
*/
public boolean isInOverrideZone( int x, int y ){
double factorW = getWidthFactor();
double factorH = getHeightFactor();
return isInOverrideZone( x, y, factorW, factorH );
}
@Override
public boolean aside( AsideRequest request ){
if( child == null ){
if( request.getPlaceholder() != null ){
Placeholder placeholder = createPlaceholder( -1 );
setChild( placeholder );
return placeholder.aside( request );
}
}
else{
return child.aside( request );
}
return true;
}
@Override
public boolean aside( SplitDockPathProperty property, int index, AsideRequest request ){
if( child == null ){
if( request.getPlaceholder() != null ){
long id = property.getLeafId();
Placeholder placeholder = createPlaceholder( id );
setChild( placeholder );
placeholder.addPlaceholder( request.getPlaceholder() );
}
return true;
}
else{
return child.aside( property, index, request );
}
}
@Override
public void evolve( SplitDockTree<Dockable>.Key key, boolean checkValidity, Map<Leaf, Dockable> linksToSet ){
setChild( create( key, checkValidity, linksToSet ) );
}
@Override
public boolean insert( SplitDockPlaceholderProperty property, Dockable dockable ){
boolean done = child != null && child.insert( property, dockable );
if( !done ){
return getAccess().drop( dockable, property.toSplitLocation( this ), this );
}
return true;
}
@Override
public boolean insert( SplitDockPathProperty property, int depth, Dockable dockable ) {
if( child == null ){
long id = property.getLeafId();
Leaf leaf = create( dockable, id );
if( leaf == null )
return false;
setChild( leaf );
leaf.setDockable( dockable, null );
return true;
}
else
return child.insert( property, depth, dockable );
}
@Override
public <N> N submit( SplitTreeFactory<N> factory ) {
if( child == null )
return factory.root( null, getId() );
else
return factory.root( child.submit( factory ), getId() );
}
@Override
public boolean isVisible(){
return true;
}
@Override
public SplitNode getVisible(){
return this;
}
@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 );
if( child != null ){
Rectangle bounds = new Rectangle( (int)(x * factorW), (int)(y * factorH), (int)(width * factorW), (int)(height * factorH));
Rectangle refitted = getAccess().getSpanStrategy().modifyBounds( bounds, this );
if( !bounds.equals( refitted ) && factorW > 0 && factorH > 0 ){
x = refitted.x / factorW;
y = refitted.y / factorH;
width = refitted.width / factorW;
height = refitted.height / factorH;
}
child.updateBounds( x, y, width, height, factorW, factorH, components );
}
treeChanged = false;
}
@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 );
treeChanged = false;
}
@Override
public void onSpanResize(){
getStation().updateBounds();
}
@Override
public PutInfo getPut( int x, int y, double factorW, double factorH, Dockable drop ) {
if( !getBounds().contains( x, y ))
return null;
if( child != null )
return child.getPut( x, y, factorW, factorH, drop );
else
return null;
}
@Override
public boolean isInOverrideZone( int x, int y, double factorW, double factorH ) {
if( !getBounds().contains( x, y ))
return false;
if( child != null )
return child.isInOverrideZone( x, y, factorW, factorH );
else
return false;
}
@Override
public Leaf getLeaf( Dockable dockable ) {
return child == null ? null : child.getLeaf( dockable );
}
@Override
public Node getDividerNode( int x, int y ) {
if( child == null )
return null;
else
return child.getDividerNode( x, y );
}
@Override
public void visit( SplitNodeVisitor visitor ) {
visitor.handleRoot( this );
if( child != null )
child.visit( visitor );
}
@Override
public void toString( int tabs, StringBuilder out ) {
out.append( "Root [id=" );
out.append( getId() );
out.append( "]\n" );
for( int i = 0; i < tabs+1; i++ )
out.append( '\t' );
if( child != null )
child.toString( tabs+1, out );
else
out.append( "<null>" );
}
}