/*
* 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.Iterator;
import java.util.LinkedList;
import java.util.List;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.layout.AbstractDockableProperty;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.util.Version;
import bibliothek.util.xml.XElement;
/**
* A {@link DockableProperty} used by the {@link SplitDockStation} to describe
* the location of a {@link Dockable} in the tree of all children of the station.
* @author Benjamin Sigg
*/
public class SplitDockPathProperty extends AbstractDockableProperty implements Iterable<SplitDockPathProperty.Node>{
/** The direction which the path takes */
public static enum Location{
/** the tree splits horizontally and the path is to the left */
LEFT,
/** the tree splits horizontally and the path is to the right */
RIGHT,
/** the tree splits vertically and the path is to the top */
TOP,
/** the tree splits vertically and the path is to the bottom */
BOTTOM
}
/** the path, where the first entry is the node nearest to the root */
private List<Node> nodes = new LinkedList<Node>();
/** the identifier of the leaf that has been stored in this path */
private long leafId = -1;
/**
* Creates a new, empty path
*/
public SplitDockPathProperty(){
// do nothing
}
public DockableProperty copy() {
SplitDockPathProperty copy = new SplitDockPathProperty();
for( Node node : nodes ){
copy.add( node.getLocation(), node.getSize(), node.getId() );
}
copy.setLeafId( getLeafId() );
copy( copy );
return copy;
}
public Iterator<SplitDockPathProperty.Node> iterator() {
return nodes.iterator();
}
/**
* Gets the number of nodes stores in this property.
* @return the number of nodes
*/
public int size(){
return nodes.size();
}
/**
* Gets the index'th node, where the node 0 is the node nearest to the
* root.
* @param index the index of the node
* @return the node
*/
public SplitDockPathProperty.Node getNode( int index ){
return nodes.get( index );
}
/**
* Sets the unique identifier of the leaf that was stored in this path.
* @param leafId the id or -1
*/
public void setLeafId( long leafId ){
this.leafId = leafId;
}
/**
* Gets the unique identifier of the leaf that was stored in this path.
* @return the id or -1
*/
public long getLeafId(){
return leafId;
}
/**
* Gets the last node of this path.
* @return the last node or <code>null</code> if this path is empty
*/
public SplitDockPathProperty.Node getLastNode(){
if( nodes.isEmpty() )
return null;
return nodes.get( nodes.size()-1 );
}
/**
* Calculates which bounds the element accessed through the given path would
* have.
* @return the bounds
*/
public SplitDockProperty toLocation(){
double x = 0, y = 0, w = 1, h = 1;
for( Node node : nodes ){
switch( node.getLocation() ){
case LEFT:
w = w * node.getSize();
break;
case RIGHT:
x = x + w - w * node.getSize();
w = w * node.getSize();
break;
case TOP:
h = h * node.getSize();
break;
case BOTTOM:
y = y + h - h * node.getSize();
h = h * node.getSize();
break;
}
}
SplitDockProperty property = new SplitDockProperty( x, y, w, h );
property.setSuccessor( getSuccessor() );
return property;
}
/**
* Calculates which bounds the element accessed through this path would have. This
* method assumes that <code>onPath</code> is a node that is on this path and
* first searches for an item with the same unique identifier like <code>onPath</code>. If
* such an item is found, then the current boundaries of <code>onPath</code> are taken
* as start parameters.
* @param onPath some node which might be on this path
* @return the boundaries
*/
public SplitDockProperty toLocation( SplitNode onPath ){
if( onPath == null || onPath.getId() == -1 )
return toLocation();
SplitDockProperty result;
long id = onPath.getId();
if( getLeafId() == id ){
result = new SplitDockProperty( onPath.getX(), onPath.getY(), onPath.getWidth(), onPath.getHeight() );
}
else{
double x = 0, y = 0, w = 1, h = 1;
for( Node node : nodes ){
if( node.getId() == id ){
x = onPath.getX();
y = onPath.getY();
w = onPath.getWidth();
h = onPath.getHeight();
}
switch( node.getLocation() ){
case LEFT:
w = w * node.getSize();
break;
case RIGHT:
x = x + w - w * node.getSize();
w = w * node.getSize();
break;
case TOP:
h = h * node.getSize();
break;
case BOTTOM:
y = y + h - h * node.getSize();
h = h * node.getSize();
break;
}
}
result = new SplitDockProperty( x, y, w, h );
}
result.setSuccessor( getSuccessor() );
return result;
}
/**
* Adds a new element to the end of the path. Every element describes which turn the
* path takes in a node.
* @param location the direction into which the path goes
* @param size the relative size of the path, a value in the range 0.0
* to 1.0
* @param id the unique identifier of the node or -1
*/
public void add( Location location, double size, long id ){
insert( location, size, nodes.size(), id );
}
/**
* Adds a new element to the path. Every element describes which turn the
* path takes in a node.
* @param location the direction into which the path goes
* @param size the relative size of the path, a value in the range 0.0
* to 1.0
* @param index where to add the new element
* @param id the unique identifier of the new node or -1
*/
public void insert( Location location, double size, int index, long id ){
if( location == null )
throw new NullPointerException( "location must not be null" );
if( size < 0 || size > 1.0 )
throw new IllegalArgumentException( "size must be in the range 0.0 to 1.0");
nodes.add( index, new Node( location, size, id ) );
}
public String getFactoryID() {
return SplitDockPathPropertyFactory.ID;
}
public void store( DataOutputStream out ) throws IOException {
Version.write( out, Version.VERSION_1_0_8 );
out.writeInt( nodes.size() );
for( Node node : nodes ){
switch( node.getLocation() ){
case LEFT:
out.writeByte( 0 );
break;
case RIGHT:
out.writeByte( 1 );
break;
case TOP:
out.writeByte( 2 );
break;
case BOTTOM:
out.writeByte( 3 );
break;
}
out.writeDouble( node.getSize() );
out.writeLong( node.getId() );
}
out.writeLong( leafId );
}
public void store( XElement element ) {
for( Node node : nodes ){
XElement xnode = element.addElement( "node" );
xnode.addString( "location", node.getLocation().name() );
xnode.addDouble( "size", node.getSize() );
if( node.getId() >= 0 ){
xnode.addLong( "id", node.getId() );
}
}
element.addElement( "leaf" ).addLong( "id", leafId );
}
public void load( DataInputStream in ) throws IOException {
Version version = Version.read( in );
version.checkCurrent();
boolean version8 = Version.VERSION_1_0_8.compareTo( version ) <= 0;
nodes.clear();
int count = in.readInt();
while( count > 0 ){
count--;
Location location = null;
switch( in.readByte() ){
case 0:
location = Location.LEFT;
break;
case 1:
location = Location.RIGHT;
break;
case 2:
location = Location.TOP;
break;
case 3:
location = Location.BOTTOM;
break;
}
double size = in.readDouble();
long id = -1;
if( version8 ){
id = in.readLong();
}
nodes.add( new Node( location, size, id ) );
}
if( version8 ){
leafId = in.readLong();
}
else{
leafId = -1;
}
}
public void load( XElement element ) {
nodes.clear();
for( XElement xnode : element.getElements( "node" )){
Location location = Location.valueOf( xnode.getString( "location" ));
double size = xnode.getDouble( "size" );
long id = -1;
if( xnode.attributeExists( "id" )){
id = xnode.getLong( "id" );
}
nodes.add( new Node( location, size, id ) );
}
XElement xleaf = element.getElement( "leaf" );
if( xleaf != null ){
leafId = xleaf.getLong( "id" );
}
else{
leafId = -1;
}
}
@Override
public String toString() {
return getClass().getName() + "[nodes="+ nodes +", leaf=" + leafId + "]";
}
@Override
public int hashCode(){
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((nodes == null) ? 0 : nodes.hashCode());
return result;
}
@Override
public boolean equals( Object obj ){
if( this == obj ) {
return true;
}
if( !super.equals( obj ) ) {
return false;
}
if( obj.getClass() == this.getClass() ) {
SplitDockPathProperty other = (SplitDockPathProperty)obj;
if( nodes == null ){
if( other.nodes != null )
return false;
}else if( !nodes.equals( other.nodes ) )
return false;
return true;
}
return false;
}
/**
* Describes one turn of the path.
* @author Benjamin Sigg
*/
public static class Node{
/** the amount of space the path gets in this turn */
private double size;
/** the direction into which the path goes */
private Location location;
/** the unique id of this node */
private long id;
/**
* Creates a new turn.
* @param location the direction into which the path goes
* @param size the amount of space the path gets in this turn
* @param id the unique id of this node or -1
*/
public Node( Location location, double size, long id ){
this.location = location;
this.size = size;
this.id = id;
}
/**
* Gets the amount of space the path gets in this turn
* @return the amount of space, a value in the range 0.0 to 1.0.
*/
public double getSize() {
return size;
}
/**
* Gets the direction into which the path goes
* @return the direction
*/
public Location getLocation() {
return location;
}
/**
* Gets the unique id of the {@link SplitNode} that is represented
* by this node.
* @return the unique id, might be -1
*/
public long getId(){
return id;
}
@Override
public String toString() {
return "[size=" + size + ", location=" + location + ", id=" + id + "]";
}
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result
+ ((location == null) ? 0 : location.hashCode());
long temp;
temp = Double.doubleToLongBits( size );
result = prime * result + (int)(temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals( Object obj ){
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( this.getClass() == obj.getClass() ) {
Node other = (Node)obj;
if( location == null ){
if( other.location != null )
return false;
}else if( !location.equals( other.location ) )
return false;
if( Double.doubleToLongBits( size ) != Double
.doubleToLongBits( other.size ) )
return false;
return true;
}
return false;
}
}
}