/*
* 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) 2012 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.extension.css.tree;
import java.util.HashMap;
import java.util.Map;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.event.DockRegisterListener;
import bibliothek.gui.dock.extension.css.CssNode;
import bibliothek.gui.dock.extension.css.CssPath;
import bibliothek.gui.dock.extension.css.doc.CssDocKey;
import bibliothek.gui.dock.extension.css.doc.CssDocPath;
import bibliothek.gui.dock.extension.css.doc.CssDocPathNode;
import bibliothek.gui.dock.extension.css.doc.CssDocText;
import bibliothek.gui.dock.extension.css.path.FlapDockStationNode;
import bibliothek.gui.dock.extension.css.path.NamedCssNode;
import bibliothek.gui.dock.extension.css.path.ScreenDockStationNode;
import bibliothek.gui.dock.extension.css.path.SplitDockStationNode;
import bibliothek.gui.dock.extension.css.path.StackDockStationNode;
import bibliothek.util.Todo;
import bibliothek.util.Todo.Compatibility;
import bibliothek.util.Todo.Priority;
import bibliothek.util.Todo.Version;
/**
* {@link CssTree} provides a {@link CssPath} for each {@link DockElement}, the paths
* share {@link CssNode}s whenever possible. Each path is automatically updated whenever
* a property associated with a {@link DockElement} (e.g. its location) changes.
* @author Benjamin Sigg
*/
public class CssTree {
/** the controller in whose realm this tree works */
private DockController controller;
/** factories for creating relation {@link CssNode}s */
private Map<Class<?>, CssRelationNodeFactory<?>> relationFactories = new HashMap<Class<?>, CssRelationNodeFactory<?>>();
/** factories for creating {@link CssNode}s */
private Map<Class<?>, CssNodeFactory<?>> nodeFactories = new HashMap<Class<?>, CssNodeFactory<?>>();
/** cached paths for the known existing {@link DockElement}s */
private Map<DockElement, CssPath> pathCache = new HashMap<DockElement, CssPath>();
/** cached nodes for the known existing {@link DockElement}s */
private Map<DockElement, CssNode> selfCache = new HashMap<DockElement, CssNode>();
private boolean bound = false;
private DockRegisterListener registerListener = new DockRegisterAdapter(){
@Override
public void dockableUnregistered( DockController controller, Dockable dockable ){
pathCache.remove( dockable );
selfCache.remove( dockable );
}
@Override
public void dockStationUnregistered( DockController controller, DockStation station ){
pathCache.remove( station );
selfCache.remove( station );
}
};
/**
* Creates a new tree.
* @param controller the controller in whose realm this tree has to work
*/
public CssTree( DockController controller ){
if( controller == null ){
throw new IllegalArgumentException( "controller must not be null" );
}
this.controller = controller;
initRelationFactories();
initNodeFactories();
}
/**
* Informs this tree that it is in use and can acquire resources.
*/
public void bind(){
bound = true;
controller.getRegister().addDockRegisterListener( registerListener );
}
/**
* Informs this tree that it is no longer in use and should release all resources.
*/
public void unbind(){
bound = false;
controller.getRegister().removeDockRegisterListener( registerListener );
pathCache.clear();
selfCache.clear();
}
private void initRelationFactories(){
putRelationFactory( StackDockStation.class, new CssRelationNodeFactory<StackDockStation>(){
@Override
public CssNode createRelation( StackDockStation parent, Dockable child ){
return new StackDockStationNode( parent, child );
}
});
putRelationFactory( ScreenDockStation.class, new CssRelationNodeFactory<ScreenDockStation>(){
@Override
public CssNode createRelation( ScreenDockStation parent, Dockable child ){
return new ScreenDockStationNode( parent, child );
}
});
putRelationFactory( FlapDockStation.class, new CssRelationNodeFactory<FlapDockStation>(){
@Override
public CssNode createRelation( FlapDockStation parent, Dockable child ){
return new FlapDockStationNode( parent, child );
}
});
putRelationFactory( SplitDockStation.class, new CssRelationNodeFactory<SplitDockStation>(){
@Override
public CssNode createRelation( SplitDockStation parent, Dockable child ){
return new SplitDockStationNode( parent, child );
}
});
}
@Todo( compatibility=Compatibility.COMPATIBLE, priority=Priority.MAJOR,
target=Version.VERSION_1_1_2, description="have better self nodes, e.g. a dockable could have a property for its title text")
private void initNodeFactories(){
putFactory( StackDockStation.class, new CssNodeFactory<StackDockStation>(){
@Override
public CssNode create( StackDockStation object ){
return new NamedCssNode( "stack" );
}
});
putFactory( FlapDockStation.class, new CssNodeFactory<FlapDockStation>(){
@Override
public CssNode create( FlapDockStation object ){
return new NamedCssNode( "flap" );
}
});
putFactory( ScreenDockStation.class, new CssNodeFactory<ScreenDockStation>(){
@Override
public CssNode create( ScreenDockStation object ){
return new NamedCssNode( "screen" );
}
});
putFactory( SplitDockStation.class, new CssNodeFactory<SplitDockStation>(){
@Override
public CssNode create( SplitDockStation object ){
return new NamedCssNode( "split" );
}
});
putFactory( Dockable.class, new CssNodeFactory<Dockable>(){
public CssNode create( Dockable element ){
return new NamedCssNode( "dockable" );
}
});
putFactory( DockElement.class, new CssNodeFactory<DockElement>(){
public CssNode create( DockElement element ){
return new NamedCssNode( "element" );
}
});
}
/**
* Adds a factory to this tree. The factory will be used by the method {@link #getRelationNode(DockElement)}.
* @param clazz the type of {@link DockStation} that can be managed by <code>factory</code>
* @param factory the new factory
*/
public <S extends DockStation> void putRelationFactory( Class<S> clazz, CssRelationNodeFactory<S> factory ){
relationFactories.put( clazz, factory );
}
/**
* Adds a factory to this tree. The factory will be used to create {@link CssNode}s describing various
* {@link Object}s.
* @param clazz the clazz to convert
* @param factory the factory for creating new {@link CssNode}s from {@link Object}s of type <code>clazz</code>
*/
public <S> void putFactory( Class<S> clazz, CssNodeFactory<S> factory ){
nodeFactories.put( clazz, factory );
}
/**
* Gets or creates a path pointing to <code>element</code>.
* @param element the element to which the {@link CssPath} should point
* @return the path, may be a new {@link CssPath} or may be shared with other modules
*/
@CssDocPath(
id="getPathFor",
description=@CssDocText(text="Generic path for a Dockable or a DockStation, the nodes are created by different CssNodeFactorys."),
unordered={
@CssDocPathNode(name=@CssDocKey(key="split", description=@CssDocText(text="Denotes a SplitDockStation when using the default CssNodeFactories"))),
@CssDocPathNode(name=@CssDocKey(key="flap", description=@CssDocText(text="Denotes a FlapDockStation when using the default CssNodeFactories"))),
@CssDocPathNode(name=@CssDocKey(key="stack", description=@CssDocText(text="Denotes a StackDockStation when using the default CssNodeFactories"))),
@CssDocPathNode(name=@CssDocKey(key="screen", description=@CssDocText(text="Denotes a ScreenDockStation when using the default CssNodeFactories"))),
@CssDocPathNode(name=@CssDocKey(key="dockable", description=@CssDocText(text="Denotes a Dockable when using the default CssNodeFactories"))),
@CssDocPathNode(name=@CssDocKey(key="element", description=@CssDocText(text="Denotes a generic DockElement when using the default CssNodeFactories"))),
@CssDocPathNode(reference=StackDockStationNode.class),
@CssDocPathNode(reference=ScreenDockStationNode.class),
@CssDocPathNode(reference=SplitDockStationNode.class),
@CssDocPathNode(reference=FlapDockStationNode.class),
})
public CssPath getPathFor( DockElement element ){
CssPath path = pathCache.get( element );
if( path == null ){
path = new DockElementPath( this, element );
}
if( bound ){
pathCache.put( element, path );
}
return path;
}
/**
* Gets the node that describes <code>element</code> itself.
* @param element the element whose description is searched
* @return the description to <code>element</code>
*/
public CssNode getSelfNode( DockElement element ){
CssNode node = selfCache.get( element );
if( node == null ){
node = create( element.getClass(), element );
}
if( bound ){
selfCache.put( element, node );
}
return node;
}
@SuppressWarnings("unchecked")
private CssNode create( Class<?> type, Object element ){
if( type == null ){
return null;
}
CssNodeFactory<Object> factory = (CssNodeFactory<Object>)nodeFactories.get( type );
if( factory != null ){
return factory.create( element );
}
for( Class<?> interfaze : type.getInterfaces()){
CssNode result = create( interfaze, element );
if( result != null ){
return result;
}
}
return create( type.getSuperclass(), element );
}
/**
* Gets the node that describes the current relation between <code>element</code>
* and its parent {@link DockStation} (if there is any). This node is not updated
* if the relationship is broken.
* @param element the element whose relation is searched
* @return the relation to the parent station or <code>null</code>
*/
@SuppressWarnings("unchecked")
public CssNode getRelationNode( DockElement element ){
Dockable dockable = element.asDockable();
if( dockable == null ){
return null;
}
DockStation parent = dockable.getDockParent();
if( parent == null ){
return null;
}
Class<?> clazz = parent.getClass();
while( clazz != null ){
CssRelationNodeFactory<DockStation> factory = (CssRelationNodeFactory<DockStation>)relationFactories.get( clazz );
if( factory == null ){
clazz = clazz.getSuperclass();
}
else{
return factory.createRelation( parent, dockable );
}
}
return null;
}
}