/*
* 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.frontend;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
import bibliothek.gui.DockFrontend;
import bibliothek.gui.dock.layout.*;
import bibliothek.util.Version;
import bibliothek.util.xml.XAttribute;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XException;
/**
* The set of properties that describe one setting of a {@link DockFrontend}.
* @author Benjamin Sigg
*/
public class Setting{
/** the layouts of the roots */
private Map<String, DockLayoutComposition> roots = new HashMap<String, DockLayoutComposition>();
/** the list of element that are not visible */
private List<Invisible> dockables = new ArrayList<Invisible>();
/**
* Stores the layout of a root.
* @param root the name of the root
* @param layout the layout
*/
public void putRoot( String root, DockLayoutComposition layout ){
roots.put( root, layout );
}
/**
* Gets the layout of a root.
* @param root the root
* @return the layout or <code>null</code>
*/
public DockLayoutComposition getRoot( String root ){
return roots.get( root );
}
/**
* Gets the keys of all known roots.
* @return the keys of the roots
*/
public String[] getRootKeys(){
Set<String> keys = roots.keySet();
return keys.toArray( new String[ keys.size() ] );
}
/**
* Stores the location of an invisible element.
* @param key the key of the element
* @param root the preferred root of the element
* @param layout the layout of the element, may be <code>null</code>
* @param location the location of the element on <code>root</code>
*/
public void addInvisible( String key, String root, DockLayoutComposition layout, DockableProperty location ){
Invisible invisible = new Invisible();
invisible.key = key;
invisible.root = root;
invisible.location = location;
invisible.layout = layout;
dockables.add( invisible );
}
/**
* Gets the number of stored invisible elements.
* @return the number of elements
*/
public int getInvisibleCount(){
return dockables.size();
}
/**
* Gets the key of the index'th invisible element.
* @param index the index of the element
* @return the key
*/
public String getInvisibleKey( int index ){
return dockables.get( index ).key;
}
/**
* Gets the preferred root of the index'th invisible element.
* @param index the index of the element
* @return the root
*/
public String getInvisibleRoot( int index ){
return dockables.get( index ).root;
}
/**
* Gets the location of the index'th invisible element.
* @param index the index of the element
* @return the location
*/
public DockableProperty getInvisibleLocation( int index ){
return dockables.get( index ).location;
}
/**
* Gets the layout of the index'th invisible element.
* @param index the index of the layout
* @return associated information, may be <code>null</code>
*/
public DockLayoutComposition getInvisibleLayout( int index ){
return dockables.get( index ).layout;
}
/**
* Using the factories given by <code>situation</code>, this method tries
* to fill any gaps in the layout.
* @param situation a set of factories to use
* @throws IllegalArgumentException if <code>situation</code> cannot handle
* the content of this setting
*/
public void fillMissing( DockSituation situation ){
try{
for( Map.Entry<String, DockLayoutComposition> entry : roots.entrySet() ){
entry.setValue( situation.fillMissing( entry.getValue() ) );
}
}
catch( IOException ex ){
throw new IllegalArgumentException( ex );
}
catch( XException ex ){
throw new IllegalArgumentException( ex );
}
}
/**
* Writes the properties of this setting into <code>out</code>.
* @param situation can be used to write {@link DockLayout}s
* @param transformer can be used to write {@link DockableProperty}s
* @param entry if <code>true</code>, then this setting is used as one of
* the settings a user can choose. If <code>false</code> then this setting
* is used as the final setting that is written when the application
* shuts down.
* @param out a stream to write into
* @throws IOException if an I/O-error occurs
*/
public void write( DockSituation situation, PropertyTransformer transformer, boolean entry, DataOutputStream out ) throws IOException{
Version.write( out, Version.VERSION_1_0_7 );
String[] roots = getRootKeys();
out.writeInt( roots.length );
for( String root : roots ){
out.writeUTF( root );
situation.writeComposition( getRoot( root ), out );
}
out.writeInt( getInvisibleCount() );
for( int i = 0, n = getInvisibleCount(); i<n; i++ ){
out.writeUTF( getInvisibleKey( i ) );
String root = getInvisibleRoot( i );
if( root == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
out.writeUTF( root );
}
DockableProperty location = getInvisibleLocation( i );
if( location == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
transformer.write( getInvisibleLocation( i ), out );
}
DockLayoutComposition layout = getInvisibleLayout( i );
if( layout == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
situation.writeComposition( layout, out );
}
}
}
/**
* Writes the properties of this setting in xml format.
* @param situation can be used to write {@link DockLayout}s
* @param transformer can be used to write {@link DockableProperty}s
* @param entry if <code>true</code>, then this setting is used as one of
* the settings a user can choose. If <code>false</code> then this setting
* is used as the final setting that is written when the application
* shuts down.
* @param element the element into which this setting writes, the attributes
* of <code>element</code> are not changed.
*/
public void writeXML( DockSituation situation, PropertyTransformer transformer, boolean entry,XElement element ){
XElement xroots = element.addElement( "roots" );
String[] roots = getRootKeys();
for( String root : roots ){
XElement xroot = xroots.addElement( "root" );
xroot.addString( "name", root );
situation.writeCompositionXML( getRoot( root ), xroot );
}
XElement xchildren = element.addElement( "children" );
for( int i = 0, n = getInvisibleCount(); i<n; i++ ){
XElement xchild = xchildren.addElement( "child" );
xchild.addString( "key", getInvisibleKey( i ) );
String root = getInvisibleRoot( i );
if( root != null ){
xchild.addString( "root", root );
}
DockableProperty location = getInvisibleLocation( i );
if( location == null ){
xchild.addBoolean( "location", false );
}
else{
xchild.addBoolean( "location", true );
transformer.writeXML( getInvisibleLocation( i ), xchild.addElement( "location" ) );
}
DockLayoutComposition layout = getInvisibleLayout( i );
if( layout != null ){
situation.writeCompositionXML( layout, xchild.addElement( "layout" ) );
}
}
}
/**
* Reads the properties of this setting. Old properties are deleted without
* further notice.
* @param situation can be used to read {@link DockLayout}s
* @param transformer can be used to read {@link DockableProperty}s
* @param entry if <code>true</code>, then this setting is used as one of
* the settings a user can choose. If <code>false</code> then this setting
* is used as the first setting that is read when the application
* starts up.
* @param in the stream to read from
* @throws IOException if an I/O-error occurs
*/
public void read( DockSituation situation, PropertyTransformer transformer, boolean entry,DataInputStream in ) throws IOException{
Version version = Version.read( in );
version.checkCurrent();
boolean version7 = Version.VERSION_1_0_7.compareTo( version ) <= 0;
roots.clear();
dockables.clear();
int count = in.readInt();
for( int i = 0; i < count; i++ ){
String root = in.readUTF();
DockLayoutComposition layout = situation.readComposition( in );
if( layout != null ){
putRoot( root, layout );
}
}
count = in.readInt();
for( int i = 0; i < count; i++ ){
String key = in.readUTF();
String root = null;
if( version7 ){
if( in.readBoolean() ){
root = in.readUTF();
}
}
else{
root = in.readUTF();
}
DockableProperty location = null;
if( version7 ){
if( in.readBoolean() ){
location = transformer.read( in );
}
}
else{
location = transformer.read( in );
}
DockLayoutComposition layout = null;
if( version7 ){
if( in.readBoolean() ){
layout = situation.readComposition( in );
}
}
addInvisible( key, root, layout, location );
}
}
/**
* Reads the properties of this setting. Old properties are deleted without
* further notice.
* @param situation can be used to read {@link DockLayout}s
* @param transformer can be used to read {@link DockableProperty}s
* @param entry if <code>true</code>, then this setting is used as one of
* the settings a user can choose. If <code>false</code> then this setting
* is used as the first setting that is read when the application
* starts up.
* @param element the element which should be read
*/
public void readXML( DockSituation situation, PropertyTransformer transformer, boolean entry, XElement element ){
roots.clear();
dockables.clear();
// roots
XElement xroots = element.getElement( "roots" );
if( xroots != null ){
for( XElement xroot : xroots.getElements( "root" )){
String name = xroot.getString( "name" );
DockLayoutComposition composition = situation.readCompositionXML( xroot );
if( composition != null ){
putRoot( name, composition );
}
}
}
// children
XElement xchildren = element.getElement( "children" );
if( xchildren != null ){
for( XElement xchild : xchildren.getElements( "child" )){
String key = xchild.getString( "key" );
String root = null;
XAttribute aroot = xchild.getAttribute( "root" );
if( aroot != null ){
root = aroot.getString();
}
boolean oldStyle = true;
boolean hasLocation = false;
XAttribute alocation = xchild.getAttribute( "location" );
if( alocation != null ){
oldStyle = false;
hasLocation = alocation.getBoolean();
}
DockableProperty location = null;
DockLayoutComposition layout = null;
if( oldStyle ){
location = transformer.readXML( xchild );
}
else{
if( hasLocation ){
XElement xlocation = xchild.getElement( "location" );
location = transformer.readXML( xlocation );
}
XElement xlayout = xchild.getElement( "layout" );
if( xlayout != null ){
layout = situation.readCompositionXML( xlayout );
}
}
addInvisible( key, root, layout, location );
}
}
}
/**
* Describes the location of an invisible element.
* @author Benjamin Sigg
*/
private static class Invisible{
/** the key of the element */
public String key;
/** the preferred root of the element */
public String root;
/** the location of the element on <code>root</code> */
public DockableProperty location;
/** information of the element itself, may be <code>null</code> */
public DockLayoutComposition layout;
}
}