/*
* 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.facile.station.split;
import java.awt.Rectangle;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.station.split.DefaultSplitLayoutManager;
import bibliothek.gui.dock.station.split.Leaf;
import bibliothek.gui.dock.station.split.Node;
import bibliothek.gui.dock.station.split.Root;
import bibliothek.gui.dock.station.split.SplitLayoutManager;
import bibliothek.gui.dock.station.split.SplitNode;
/**
* A {@link SplitLayoutManager} that can lock the size of some {@link Dockable}s
* during resize. This class is intended to be subclassed.
* @param <T> the type of the temporary data this manager works with
* @author Benjamin Sigg
*/
public abstract class LockedResizeLayoutManager<T> extends DelegatingSplitLayoutManager{
/**
* Tells how to merge the {@link ResizeRequest}s of this manager.
*/
private ConflictResolver<T> conflictResolver = new DefaultConflictResolver<T>();
/**
* Creates a new manager using the {@link DefaultSplitLayoutManager}
* as delegate.
*/
public LockedResizeLayoutManager(){
this( new DefaultSplitLayoutManager() );
}
/**
* Creates a new manager.
* @param delegate the base functionality
*/
public LockedResizeLayoutManager( SplitLayoutManager delegate ){
super( delegate );
}
/**
* Sets the {@link ConflictResolver} that will determine how to merge
* {@link ResizeRequest}s and how to resolve conflicts.
* @param conflictResolver the new policy, not <code>null</code>
*/
public void setConflictResolver( ConflictResolver<T> conflictResolver ) {
if( conflictResolver == null )
throw new IllegalArgumentException( "conflictResolver must not be null" );
this.conflictResolver = conflictResolver;
}
/**
* Gets the policy that tells how two {@link ResizeRequest}s are merged.
* @return the policy
*/
public ConflictResolver<T> getConflictResolver() {
return conflictResolver;
}
@Override
public void updateBounds( Root root, double x, double y, double factorW, double factorH ) {
Rectangle current = root.getCurrentBounds();
Rectangle bounds = root.getBounds();
boolean resize = isResize( root );
if( resize ){
resize = current.width > 10 && current.height > 10 && bounds.width > 10 && bounds.height > 10;
}
if( resize ){
updateBoundsLocked( root, x, y, factorW, factorH );
}
else if( hasTreeChanged( root )){
updateBoundsLocked( root, x, y, factorW, factorH );
}
else{
super.updateBounds( root, x, y, factorW, factorH );
}
}
/**
* Tells whether the current operation is a resize operation. The locked sizes will only be respected if
* the operation is a resize operation.
* @param root the item that is going to be updated
* @return whether a resize operation is in progress
*/
protected boolean isResize( Root root ){
Rectangle current = root.getCurrentBounds();
Rectangle bounds = root.getBounds();
return !current.equals( bounds );
}
/**
* Tells whether the current operation happens because the tree has changed (e.g. a leaf has been added or removed).
* This method is only called if {@link #isResize(Root)} already returned <code>false</code>.
* @param root the item that is going to be updated
* @return whether the tree has changed
*/
protected boolean hasTreeChanged( Root root ){
return root.hasTreeChanged();
}
/**
* Updates the bounds of <code>root</code> and all its children and does
* consider all {@link ResizeRequest}.
* @param root the root element of a tree to update
* @param x the left coordinate of <code>root</code>
* @param y the top coordinate of <code>root</code>
* @param factorW a factor all x-coordinates have to be multiplied with
* in order to get the pixel coordinates
* @param factorH a factor all y-coordinates have to be multiplied with
* in order to get the pixel coordinates
*/
public void updateBoundsLocked( Root root, double x, double y, double factorW, double factorH ){
ResizeElement<T> element = toElement( null, root );
element.prepareResize();
root.updateBounds( x, y, 1, 1, factorW, factorH, false );
element.prepareRequests();
element.adapt( 0, 0 );
root.updateBounds( x, y, 1, 1, factorW, factorH, true );
}
/**
* Gets the size request that changes the size of <code>leaf</code> such
* that it has a valid size again.
* @param t the data that was created in {@link #prepareResize(Leaf)} or <code>null</code>
* @param leaf the leaf which size is not yet valid.
* @return the preferred size or <code>null</code>
*/
public abstract ResizeRequest getRequest( T t, Leaf leaf );
/**
* Called before the resize takes place, subclasses might store some
* properties.
* @param leaf some leaf
* @return some temporary data that gets forwarded to {@link #getRequest(Object, Leaf)},
* can be <code>null</code>
*/
public abstract T prepareResize( Leaf leaf );
/**
* Transforms a {@link SplitNode} into the matching kind of {@link ResizeElement}.
* The subtree of <code>node</code> is transformed as well.
* @param parent the parent of the new element
* @param node some root, node, leaf or <code>null</code>
* @return some root, node, leaf or <code>null</code>
*/
public ResizeElement<T> toElement( ResizeElement<T> parent, SplitNode node ){
for( int i = 0; i < 5; i++ ){
ResizeElement<T> result = asyncToElement( parent, node );
if( result == null || result.isValid() ){
return result;
}
try {
Thread.sleep( 20 );
}
catch( InterruptedException e ) {
// ignore
}
}
System.err.println( "LockedResizeLayoutManager.toElement: Potential race condition detected, converting SplitNode to ResizeElement failed 5 times in a row. The node is ignored." );
return null;
}
/**
* Called by {@link #toElement(ResizeElement, SplitNode)}, tries to create a {@link ResizeElement} out of
* <code>node</code> in an environment where race conditions are possible. This is a best effort method, callers
* should check the result using the method {@link ResizeElement#isValid()}.
* @param parent the parent of the new element
* @param node some root, node, leaf or <code>null</code>
* @return some root, node, leaf or <code>null</code>
*/
protected ResizeElement<T> asyncToElement( ResizeElement<T> parent, SplitNode node ){
if( node instanceof Root ){
return new ResizeRoot<T>( this, (Root)node );
}
if( node instanceof Node ){
Node real = (Node)node;
SplitNode left = real.getLeft();
SplitNode right = real.getRight();
boolean leftVisible = left == null ? false : left.isVisible();
boolean rightVisible = right == null ? false : right.isVisible();
if( leftVisible && rightVisible ){
return new ResizeNode<T>( this, parent, (Node)node );
}
if( leftVisible ){
return toElement( parent, left.getVisible() );
}
if( rightVisible ){
return toElement( parent, right.getVisible() );
}
return null;
}
if( node instanceof Leaf ){
return new ResizeLeaf<T>( this, parent, (Leaf)node );
}
return null;
}
}