/*
* 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.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.SplitDockStation.Orientation;
import bibliothek.gui.dock.layout.DockLayoutInfo;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.layout.LocationEstimationMap;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.station.split.SplitDockPerspective.Root;
import bibliothek.gui.dock.station.split.SplitDockStationLayout.Entry;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;
import bibliothek.util.Version;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XException;
/**
* A factory that creates {@link SplitDockStation SplitDockStations}.
* @author Benjamin Sigg
*/
public class SplitDockStationFactory implements DockFactory<SplitDockStation, SplitDockPerspective, SplitDockStationLayout> {
/** The id which is normally used for this type of factory*/
public static final String ID = "SplitDockStationFactory";
/**
* Creates a new factory
*/
public SplitDockStationFactory(){
// nothing
}
public String getID() {
return ID;
}
/**
* Creates a new layout for <code>station</code>. The default implementation just calls {@link #createLayout(SplitDockStationLayout.Entry, int, boolean)}.
* @param station the station for which the layout is requested
* @param root the contents of the layout
* @param fullscreen the index of the child that is maximized
* @param hasFullscreenAction whether a fullscreen action is shown or not
* @return the new layout
*/
protected SplitDockStationLayout createLayout( SplitDockStation station, Entry root, int fullscreen, boolean hasFullscreenAction ){
return createLayout( root, fullscreen, hasFullscreenAction );
}
/**
* Creates a new layout for <code>station</code>. The default implementation just calls {@link #createLayout(SplitDockStationLayout.Entry, int, boolean)}.
* @param station the station for which the layout is requested
* @param root the contents of the layout
* @param fullscreen the index of the child that is maximized
* @param hasFullscreenAction whether a fullscreen action is shown or not
* @return the new layout
*/
protected SplitDockStationLayout createLayout( SplitDockPerspective station, Entry root, int fullscreen, boolean hasFullscreenAction ){
return createLayout( root, fullscreen, hasFullscreenAction );
}
/**
* Creates a new layout for <code>station</code>.
* @param root the contents of the layout
* @param fullscreen the index of the child that is maximized
* @param hasFullscreenAction whether a fullscreen action is shown or not
* @return the new layout
*/
protected SplitDockStationLayout createLayout( Entry root, int fullscreen, boolean hasFullscreenAction ){
return new SplitDockStationLayout( root, fullscreen, hasFullscreenAction );
}
public SplitDockStationLayout getLayout( final SplitDockStation station, final Map<Dockable, Integer> children ) {
Entry root =
station.visit( new SplitTreeFactory<Entry>(){
private PlaceholderStrategy strategy = station.getPlaceholderStrategy();
public Entry leaf( Dockable dockable, long id, Path[] placeholders, PlaceholderMap placeholderMap ){
Integer childId = children.get( dockable );
placeholders = DockUtilities.mergePlaceholders( placeholders, dockable, strategy );
if( childId != null ){
return new SplitDockStationLayout.Leaf( childId, placeholders, placeholderMap, id );
}
else if( placeholders != null && placeholders.length > 0 ){
return new SplitDockStationLayout.Leaf( -1, placeholders, placeholderMap, id );
}
else{
return null;
}
}
public Entry placeholder( long id, Path[] placeholders, PlaceholderMap placeholderMap ){
if( placeholders != null && placeholders.length > 0 ){
return new SplitDockStationLayout.Leaf( -1, placeholders, placeholderMap, id );
}
else{
return null;
}
}
public Entry root( Entry root, long id ) {
return root;
}
public Entry horizontal( Entry left, Entry right, double divider, long id, Path[] placeholders, PlaceholderMap placeholderMap, boolean visible ){
if( left == null )
return right;
if( right == null )
return left;
return new SplitDockStationLayout.Node( Orientation.HORIZONTAL, divider, left, right, placeholders, placeholderMap, id );
}
public Entry vertical( Entry top, Entry bottom, double divider, long id, Path[] placeholders, PlaceholderMap placeholderMap, boolean visible ){
if( top == null )
return bottom;
if( bottom == null )
return top;
return new SplitDockStationLayout.Node( Orientation.VERTICAL, divider, top, bottom, placeholders, placeholderMap, id );
}
});
Dockable fullscreenDockable = station.getFullScreen();
Integer fullscreen = null;
if( fullscreenDockable != null )
fullscreen = children.get( fullscreenDockable );
if( fullscreen == null )
return createLayout( station, root, -1, station.hasFullScreenAction() );
else
return createLayout( station, root, fullscreen, station.hasFullScreenAction() );
}
public SplitDockStationLayout getPerspectiveLayout( SplitDockPerspective element, Map<PerspectiveDockable, Integer> children ){
if( children == null ){
return createLayout( null, -1, element.hasFullscreenAction() );
}
Entry root = convert( element.getRoot(), children );
PerspectiveDockable fullscreenDockable = element.getFullscreen();
Integer fullscreen = null;
if( fullscreenDockable != null )
fullscreen = children.get( fullscreenDockable );
if( fullscreen == null )
return createLayout( element, root, -1, element.hasFullscreenAction() );
else
return createLayout( element, root, fullscreen, element.hasFullscreenAction() );
}
private Entry convert( SplitDockPerspective.Entry entry, Map<PerspectiveDockable, Integer> children ){
if( entry == null ){
return null;
}
if( entry.asNode() != null ){
SplitDockPerspective.Node node = entry.asNode();
Entry childA = convert( node.getChildA(), children );
Entry childB = convert( node.getChildB(), children );
if( childA == null ){
return childB;
}
if( childB == null ){
return childA;
}
return new SplitDockStationLayout.Node( node.getOrientation(), node.getDivider(), childA, childB, toArray( node.getPlaceholders() ), node.getPlaceholderMap(), node.getNodeId() );
} else if( entry.asLeaf() != null ){
SplitDockPerspective.Leaf leaf = entry.asLeaf();
Integer id = children.get( leaf.getDockable() );
return new SplitDockStationLayout.Leaf( id == null ? -1 : id.intValue(), toArray( leaf.getPlaceholders() ), leaf.getPlaceholderMap(), leaf.getNodeId() );
}
else{
return convert( ((Root)entry).getChild(), children );
}
}
private Path[] toArray( Set<Path> placeholders ){
if( placeholders == null ){
return null;
}
return placeholders.toArray( new Path[ placeholders.size() ] );
}
public void setLayout( SplitDockStation station, SplitDockStationLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ) {
DockableSplitDockTree tree = new DockableSplitDockTree();
DockableSplitDockTree.Key root = null;
if( layout.getRoot() != null ){
root = handleEntry( layout.getRoot(), tree, children );
}
if( root != null ){
tree.root( root );
}
station.dropTree( tree, false );
PlaceholderStrategy oldStrategy = station.getPlaceholderStrategy().getStrategy();
if( placeholders != oldStrategy && placeholders != null ){
try{
station.setPlaceholderStrategy( placeholders );
}
finally{
station.setPlaceholderStrategy( oldStrategy );
}
}
Dockable fullscreen = children.get( layout.getFullscreen() );
station.setFullScreen( fullscreen );
}
public SplitDockPerspective layoutPerspective( SplitDockStationLayout layout, Map<Integer, PerspectiveDockable> children ){
SplitDockPerspective result = new SplitDockPerspective();
layoutPerspective( result, layout, children );
return result;
}
public void layoutPerspective( SplitDockPerspective perspective, SplitDockStationLayout layout, Map<Integer,PerspectiveDockable> children ){
if( children != null ){
PerspectiveSplitDockTree tree = new PerspectiveSplitDockTree();
PerspectiveSplitDockTree.Key root = null;
if( layout.getRoot() != null ){
root = handleEntry( layout.getRoot(), tree, children );
}
if( root != null ){
tree.root( root );
}
perspective.read( tree, children.get( layout.getFullscreen() ) );
}
perspective.setHasFullscreenAction( layout.hasFullscreenAction() );
}
/**
* Transforms an entry of a {@link SplitDockStationLayout} into a key
* of a {@link SplitDockTree}.
* @param entry the element to transform
* @param tree the tree into which to add new keys
* @param children the set of known children
* @return the key or <code>null</code>
*/
private <D> SplitDockTree<D>.Key handleEntry( SplitDockStationLayout.Entry entry, SplitDockTree<D> tree, Map<Integer, D> children ){
if( entry.asLeaf() != null )
return handleLeaf( entry.asLeaf(), tree, children );
else
return handleNode( entry.asNode(), tree, children );
}
/**
* Transforms a leaf of a {@link SplitDockStationLayout} into a key
* of a {@link SplitDockTree}.
* @param leaf the element to transform
* @param tree the tree into which to add new keys
* @param children the set of known children
* @return the key or <code>null</code>
*/
private <D> SplitDockTree<D>.Key handleLeaf( SplitDockStationLayout.Leaf leaf, SplitDockTree<D> tree, Map<Integer, D> children ){
D dockable = children.get( leaf.getId() );
Path[] placeholders = leaf.getPlaceholders();
PlaceholderMap placeholderMap = leaf.getPlaceholderMap();
if( dockable != null ){
return tree.put( tree.array( dockable ), null, placeholders, placeholderMap, leaf.getNodeId() );
}
else if( placeholders != null && placeholders.length > 0 ){
return tree.put( tree.array( 0 ), null, placeholders, placeholderMap, leaf.getNodeId() );
}
return null;
}
/**
* Transforms a node of a {@link SplitDockStationLayout} into a key
* of a {@link SplitDockTree}.
* @param node the element to transform
* @param tree the tree into which to add new keys
* @param children the set of known children
* @return the key or <code>null</code>
*/
private <D> SplitDockTree<D>.Key handleNode( SplitDockStationLayout.Node node, SplitDockTree<D> tree,Map<Integer, D> children ){
SplitDockTree<D>.Key a = handleEntry( node.getChildA(), tree, children );
SplitDockTree<D>.Key b = handleEntry( node.getChildB(), tree, children );
if( a == null )
return b;
if( b == null )
return a;
switch( node.getOrientation() ){
case HORIZONTAL:
return tree.horizontal( a, b, node.getDivider(), node.getPlaceholders(), node.getPlaceholderMap(), node.getNodeId() );
case VERTICAL:
return tree.vertical( a, b, node.getDivider(), node.getPlaceholders(), node.getPlaceholderMap(), node.getNodeId() );
}
return null;
}
public void estimateLocations( SplitDockStationLayout layout, LocationEstimationMap children ) {
estimateLocations( layout.getRoot(), children );
}
/**
* Estimates the {@link DockableProperty location} of all leafs in the subtree
* beginning with <code>entry</code>.
* @param entry the root of the subtree to check
* @param children the children of the station
*/
private void estimateLocations( SplitDockStationLayout.Entry entry, LocationEstimationMap children ){
if( entry == null )
return;
SplitDockStationLayout.Leaf leaf = entry.asLeaf();
if( leaf != null ){
DockLayoutInfo info = children.getChild( leaf.getId() );
if( info != null ){
// should put the placeholder here
SplitDockPathProperty property = leaf.createPathProperty();
Path placeholder = info.getPlaceholder();
if( placeholder != null ){
info.setLocation( new SplitDockPlaceholderProperty( placeholder, property ) );
}
else{
info.setLocation( property );
}
for( int i = 0, n = children.getSubChildCount( leaf.getId() ); i<n; i++ ){
DockLayoutInfo subInfo = children.getSubChild( leaf.getId(), i );
placeholder = subInfo.getPlaceholder();
if( placeholder != null ){
subInfo.setLocation( new SplitDockPlaceholderProperty( placeholder, property ) );
}
}
}
}
SplitDockStationLayout.Node node = entry.asNode();
if( node != null ){
estimateLocations( node.getChildA(), children );
estimateLocations( node.getChildB(), children );
}
}
public void setLayout( SplitDockStation element, SplitDockStationLayout layout, PlaceholderStrategy placeholders ) {
// nothing to do
}
public SplitDockStation layout( SplitDockStationLayout layout, PlaceholderStrategy placeholders ) {
SplitDockStation station = createStation( layout.hasFullscreenAction() );
setLayout( station, layout, placeholders );
return station;
}
public SplitDockStation layout( SplitDockStationLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ) {
SplitDockStation station = createStation( layout.hasFullscreenAction() );
setLayout( station, layout, children, placeholders );
return station;
}
public void write( SplitDockStationLayout layout, DataOutputStream out ) throws IOException {
Version.write( out, Version.VERSION_1_1_0 );
SplitDockStationLayout.Entry root = layout.getRoot();
if( root == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
writeEntry( root, out );
}
out.writeInt( layout.getFullscreen() );
out.writeBoolean( layout.hasFullscreenAction() );
}
/**
* Writes an entry to <code>out</code>.
* @param entry the entry to store
* @param out the stream to write into
* @throws IOException if an I/O-error occurs
*/
private void writeEntry( SplitDockStationLayout.Entry entry, DataOutputStream out ) throws IOException{
out.writeLong( entry.getNodeId() );
Path[] placeholders = entry.getPlaceholders();
PlaceholderMap placeholderMap = entry.getPlaceholderMap();
int flag = 0;
if( entry.asNode() != null ){
flag |= 1;
}
if( placeholders != null && placeholders.length > 0 ){
flag |= 2;
}
if( placeholderMap != null ){
flag |= 4;
}
out.writeByte( flag );
if( placeholders != null && placeholders.length > 0 ){
out.writeInt( placeholders.length );
for( Path placeholder : placeholders ){
out.writeUTF( placeholder.toString() );
}
}
if( placeholderMap != null ){
placeholderMap.write( out );
}
if( entry.asLeaf() != null ){
out.writeInt( entry.asLeaf().getId() );
}
else{
SplitDockStationLayout.Node node = entry.asNode();
out.writeInt( node.getOrientation().ordinal() );
out.writeDouble( node.getDivider() );
writeEntry( node.getChildA(), out );
writeEntry( node.getChildB(), out );
}
}
public SplitDockStationLayout read( DataInputStream in, PlaceholderStrategy placeholders ) throws IOException {
Version version = Version.read( in );
version.checkCurrent();
boolean version8 = Version.VERSION_1_0_8.compareTo( version ) <= 0;
boolean version8a = Version.VERSION_1_0_8a.compareTo( version ) <= 0;
boolean version110 = Version.VERSION_1_1_0.compareTo( version ) <= 0;
SplitDockStationLayout.Entry root = null;
if( in.readBoolean() ){
root = readEntry( in, version8, version8a, placeholders );
}
int fullscreen = in.readInt();
boolean fullscreenAction = true;
if( version110 ){
fullscreenAction = in.readBoolean();
}
return createLayout( root, fullscreen, fullscreenAction );
}
/**
* Reads an entry from the stream.
* @param in the stream to read
* @param version8 version of file is at least 8
* @param version8a version of file is at least 8a
* @param strategy tells which placeholders are invalid
* @return the new entry
* @throws IOException if an I/O-error occurs
*/
private SplitDockStationLayout.Entry readEntry( DataInputStream in, boolean version8, boolean version8a, PlaceholderStrategy strategy ) throws IOException{
long id = -1;
if( version8 ){
id = in.readLong();
}
if( version8a ){
byte flag = in.readByte();
boolean node = (flag & 1) != 0;
boolean hasPlaceholders = (flag & 2) != 0;
boolean hasMap = (flag & 4) != 0;
Path[] placeholders = null;
if( hasPlaceholders ){
placeholders = new Path[ in.readInt() ];
for( int i = 0; i < placeholders.length; i++ ){
placeholders[i] = new Path( in.readUTF() );
}
}
PlaceholderMap placeholderMap = null;
if( hasMap ){
placeholderMap = new PlaceholderMap( in, strategy );
placeholderMap.setPlaceholderStrategy( null );
}
if( node ){
Orientation orientation = Orientation.values()[ in.readInt() ];
double divider = in.readDouble();
SplitDockStationLayout.Entry childA = readEntry( in, version8, version8a, strategy );
SplitDockStationLayout.Entry childB = readEntry( in, version8, version8a, strategy );
return new SplitDockStationLayout.Node( orientation, divider, childA, childB, placeholders, placeholderMap, id );
}
else{
return new SplitDockStationLayout.Leaf( in.readInt(), placeholders, placeholderMap, id );
}
}
else{
byte kind = in.readByte();
if( kind == 0 ){
return new SplitDockStationLayout.Leaf( in.readInt(), null, null, id );
}
if( kind == 1 ){
Orientation orientation = Orientation.values()[ in.readInt() ];
double divider = in.readDouble();
SplitDockStationLayout.Entry childA = readEntry( in, version8, version8a, strategy );
SplitDockStationLayout.Entry childB = readEntry( in, version8, version8a, strategy );
return new SplitDockStationLayout.Node( orientation, divider, childA, childB, null, null, id );
}
if( kind == 2 ){
int childId = in.readInt();
Path[] placeholders = readPlaceholders( in, strategy );
return new SplitDockStationLayout.Leaf( childId, placeholders, null, id );
}
if( kind == 3 ){
Orientation orientation = Orientation.values()[ in.readInt() ];
double divider = in.readDouble();
Path[] placeholders = readPlaceholders( in, strategy );
SplitDockStationLayout.Entry childA = readEntry( in, version8, version8a, strategy );
SplitDockStationLayout.Entry childB = readEntry( in, version8, version8a, strategy );
return new SplitDockStationLayout.Node( orientation, divider, childA, childB, placeholders, null, id );
}
throw new IOException( "unknown kind: " + kind );
}
}
private Path[] readPlaceholders( DataInputStream in, PlaceholderStrategy placeholders ) throws IOException{
int length = in.readInt();
List<Path> result = new ArrayList<Path>( length );
for( int i = 0; i < length; i++ ){
Path placeholder = new Path( in.readUTF() );
if( placeholders == null || placeholders.isValidPlaceholder( placeholder )){
result.add( placeholder );
}
}
return result.toArray( new Path[ result.size() ] );
}
public void write( SplitDockStationLayout layout, XElement element ) {
if( layout.getFullscreen() != -1 ){
element.addElement( "fullscreen" ).addInt( "id", layout.getFullscreen() );
}
element.addElement( "fullscreen-action" ).setBoolean( layout.hasFullscreenAction() );
if( layout.getRoot() != null ){
writeEntry( layout.getRoot(), element );
}
}
/**
* Writes an entry in xml format.
* @param entry the entry to write
* @param parent the parent node of the entry
*/
private void writeEntry( SplitDockStationLayout.Entry entry, XElement parent ){
XElement xchild;
if( entry.asLeaf() != null ){
xchild = parent.addElement( "leaf" );
xchild.addInt( "id", entry.asLeaf().getId() ).addLong( "nodeId", entry.getNodeId() );
}
else{
xchild = parent.addElement( "node" );
xchild.addLong( "nodeId", entry.getNodeId() );
xchild.addString( "orientation", entry.asNode().getOrientation().name() );
xchild.addDouble( "divider", entry.asNode().getDivider() );
writeEntry( entry.asNode().getChildA(), xchild );
writeEntry( entry.asNode().getChildB(), xchild );
}
Path[] placeholders = entry.getPlaceholders();
if( placeholders != null && placeholders.length > 0 ){
XElement xplaceholders = xchild.addElement( "placeholders" );
for( Path placeholder : placeholders ){
xplaceholders.addElement( "placeholder" ).setString( placeholder.toString() );
}
}
PlaceholderMap map = entry.getPlaceholderMap();
if( map != null ){
XElement xmap = xchild.addElement( "placeholder-map" );
map.write( xmap );
}
}
public SplitDockStationLayout read( XElement element, PlaceholderStrategy placeholders ) {
SplitDockStationLayout.Entry root = null;
XElement xroot = element.getElement( "node" );
if( xroot == null )
xroot = element.getElement( "leaf" );
if( xroot != null )
root = readEntry( xroot, placeholders );
int fullscreen = -1;
XElement xfullscreen = element.getElement( "fullscreen" );
if( xfullscreen != null )
fullscreen = xfullscreen.getInt( "id" );
XElement xfullscreenAction = element.getElement( "fullscreen-action" );
boolean fullscreenAction = true;
if( xfullscreenAction != null ){
fullscreenAction = xfullscreenAction.getBoolean();
}
return createLayout( root, fullscreen, fullscreenAction );
}
/**
* Transforms an xml-element into an entry.
* @param element the element that should be converted, of type "node" or
* "leaf".
* @param strategy strategy used for removing invalid placeholders
* @return the new entry
*/
private SplitDockStationLayout.Entry readEntry( XElement element, PlaceholderStrategy strategy ){
long nodeId = -1;
if( element.attributeExists( "nodeId" ) ){
nodeId = element.getLong( "nodeId" );
}
Path[] placeholders = null;
XElement xplaceholders = element.getElement( "placeholders" );
if( xplaceholders != null ){
XElement[] xchildren = xplaceholders.getElements( "placeholder" );
if( xchildren.length > 0 ){
List<Path> collection = new ArrayList<Path>( xchildren.length );
for( int i = 0; i < xchildren.length; i++ ){
Path placeholder = new Path( xchildren[i].getString() );
if( strategy == null || strategy.isValidPlaceholder( placeholder )){
collection.add( placeholder );
}
}
placeholders = collection.toArray( new Path[ collection.size() ]);
}
}
PlaceholderMap map = null;
XElement xmap = element.getElement( "placeholder-map" );
if( xmap != null ){
map = new PlaceholderMap( xmap, strategy );
map.setPlaceholderStrategy( null );
}
if( "leaf".equals( element.getName() )){
return new SplitDockStationLayout.Leaf( element.getInt( "id" ), placeholders, map, nodeId );
}
if( "node".equals( element.getName() )){
XElement[] xchildren = element.getElements( "leaf", "node" );
if( xchildren.length != 2 )
throw new XException( "node element must have exactly least two children: " + element );
return new SplitDockStationLayout.Node(
Orientation.valueOf( element.getString( "orientation" ) ),
element.getDouble( "divider" ),
readEntry( xchildren[0], strategy ),
readEntry( xchildren[1], strategy ),
placeholders,
map,
nodeId );
}
throw new XException( "element neither leaf nor node: " + element );
}
/**
* Creates new objects of {@link SplitDockStation}
* @param hasFullscreenAction whether the station did have a fullscreen-action
* @return the new instance
*/
protected SplitDockStation createStation( boolean hasFullscreenAction ){
return new SplitDockStation( hasFullscreenAction );
}
}