/* * 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) 2011 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.screen.magnet; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import bibliothek.gui.dock.ScreenDockStation; import bibliothek.gui.dock.station.screen.BoundaryRestriction; import bibliothek.gui.dock.station.screen.ScreenDockWindow; import bibliothek.gui.dock.station.screen.magnet.AttractorStrategy.Attraction; import bibliothek.gui.dock.station.screen.magnet.MagnetRequest.Side; /** * The {@link StickMagnetGraph} is a helper class for a {@link MagnetStrategy}. The {@link StickMagnetGraph} * is a directed graph without cycles telling which moved {@link ScreenDockWindow} affects which other * {@link ScreenDockWindow}, the graph is created depending on the * {@link AttractorStrategy#stick(bibliothek.gui.dock.ScreenDockStation, bibliothek.gui.Dockable, bibliothek.gui.Dockable) stickiness} * of two windows. * @author Benjamin Sigg */ public class StickMagnetGraph { /** information about all the {@link ScreenDockWindow}s */ private MagnetController controller; /** information about the event */ private MagnetRequest request; /** the node that represents the {@link ScreenDockWindow} of {@link #request} */ private DefaultNode root; /** a list containing all nodes of this graph */ private List<DefaultNode> nodes = new ArrayList<StickMagnetGraph.DefaultNode>(); /** all edges of this graph */ private List<DefaultEdge> edges = new ArrayList<StickMagnetGraph.DefaultEdge>(); public StickMagnetGraph( MagnetController controller, MagnetRequest request ){ this.controller = controller; this.request = request; } /** * Gets the root node of the graph. The root node has no ingoing edges and its * {@link Node#getWindow() window} is the same object as {@link MagnetRequest#getWindow()}. * The root node is created the first time this method is called, it is then cached. * @return the root node, never <code>null</code> */ public Node getRoot(){ if( root == null ){ ScreenDockWindow[] windows = controller.getWindows(); DefaultNode[] nodes = new DefaultNode[ windows.length ]; int index = 0; ScreenDockWindow window = request.getWindow(); while( windows[ index ] != window ){ index++; } expand( index, nodes, windows ); root = nodes[ index ]; } return root; } /** * Gets information about all known {@link ScreenDockWindow}s. * @return the information, not <code>null</code> */ public MagnetController getController(){ return controller; } /** * Gets information about the moved {@link ScreenDockWindow}. * @return the information, not <code>null</code> */ public MagnetRequest getRequest(){ return request; } @Override public String toString(){ if( root == null ){ return getClass().getSimpleName() + "[root=null]"; } else{ final StringBuilder builder = new StringBuilder(); builder.append( getClass().getSimpleName() ); builder.append( "[\nroot: " ); getRoot().visit( new Visitor(){ private int depth = 0; public boolean beginVisit( Node node, boolean revisit ){ builder.append( node.hashCode() ).append(" '").append( node.getWindow().getDockable().getTitleText() ).append( "' " ); builder.append( node.getConstraints() ).append( "\n" ); depth++; return !revisit; } public void endVisit( Edge edge ){ depth--; } public void endVisit( Node node ){ depth--; } public boolean beginVisit( Edge edge ){ for( int i = 0; i < depth; i++ ){ builder.append( " " ); } depth++; builder.append( "-> " ).append( edge.getSide().name().toLowerCase() ); builder.append( " " ); return true; } }); builder.append( "]" ); return builder.toString(); } } /** * Calls {@link Node#unmark()} on all nodes of this graph */ public void unmark(){ for( DefaultNode node : nodes ){ node.unmark(); } } /** * Builds the entire stickiness graph using a breath first search algorithm. * @param index the node whose neighbors have to be found by this method * @param nodes an array containing all nodes that may be created. An entry of <code>null</code> * at index <code>a</code> indicates that the {@link ScreenDockWindow} in <code>windows</code> at * index <code>a</code> is not yet part of the graph * @param windows all the windows of the {@link ScreenDockStation} */ protected void expand( int index, DefaultNode[] nodes, ScreenDockWindow[] windows ){ if( nodes[index] == null ) { nodes[index] = new DefaultNode( index, windows[index] ); } LinkedList<Integer> queue = new LinkedList<Integer>(); queue.add( index ); while( !queue.isEmpty() ){ index = queue.poll(); for( int i = 0, n = nodes.length; i < n; i++ ) { if( i != index ) { Side relation = relation( windows[index], windows[i] ); if( relation != null ) { if( nodes[i] == null ){ nodes[i] = new DefaultNode( i, windows[i] ); } nodes[index].add( relation, nodes[i] ); } } } for( DefaultEdge edge : nodes[ index ].getEdges() ){ if( edge.getTarget() != nodes[ index ]){ queue.offer( edge.getTarget().getIndex() ); } } } } /** * Gets the relation of <code>moved</code> to <code>fixed</code>. The relation is <code>null</code> * if the two windows to not stick together. * @param moved the window that was moved * @param fixed the window that was not moved * @return if <code>fixed</code> depends on <code>moved</code>: the side at which <code>fixed</code> * stays, <code>null</code> if <code>fixed</code> does not depend on <code>moved</code> */ protected Side relation( ScreenDockWindow moved, ScreenDockWindow fixed ){ Attraction attraction = getController().getStickiness( moved.getDockable(), fixed.getDockable() ); if( attraction == Attraction.ATTRACTED || attraction == Attraction.STRONGLY_ATTRACTED ) { MagnetController controller = getController(); if( controller.intersectHorizontally( moved, fixed, true ) ) { if( controller.distance( moved, Side.EAST, fixed, Side.WEST, true ) == 1 ) { return Side.EAST; } if( controller.distance( moved, Side.WEST, fixed, Side.EAST, true ) == 1 ) { return Side.WEST; } } if( controller.intersectVertically( moved, fixed, true ) ) { if( controller.distance( moved, Side.NORTH, fixed, Side.SOUTH, true ) == 1 ) { return Side.NORTH; } if( controller.distance( moved, Side.SOUTH, fixed, Side.NORTH, true ) == 1 ) { return Side.SOUTH; } } } return null; } /** * Compares the initial location of the root {@link ScreenDockWindow} with its * current location and moves all neighbors by the same amount. This method does * not change the size of any window. */ public void moveNeighbors(){ Rectangle initial = request.getInitialBounds( request.getWindow() ); Rectangle current = request.getResultBounds(); final int dx = current.x - initial.x; final int dy = current.y - initial.y; getRoot().visit( new Visitor(){ public boolean beginVisit( Edge edge ){ return true; } public void endVisit( Edge edge ){ // nothing } public boolean beginVisit( Node node, boolean revisit ){ if( revisit ){ return false; } ScreenDockWindow window = node.getWindow(); if( window != request.getWindow() ){ Rectangle bounds = request.getInitialBounds( window ); bounds.x += dx; bounds.y += dy; window.setWindowBounds( bounds ); } return true; } public void endVisit( Node node ){ // nothing } }); } /** * Compares the initial location and side of the root {@link ScreenDockWindow} with * its current shape and reshapes all neighbors such that with the resulting boundaries * the same graph as <code>this</code> would be created. In general this method prefers * to change only the position of {@link ScreenDockWindow}s, the method is however free * to change the size as well if it looks like a good choice. */ public void moveAndResizeNeighbors(){ Rectangle initial = request.getInitialBounds( request.getWindow() ); Rectangle current = request.getResultBounds(); StickMagnetGraphConstraint constraint = getRoot().getConstraints(); constraint.set( Side.NORTH, current.y- initial.y, true, true ); constraint.set( Side.WEST, current.x - initial.x, true, true ); constraint.set( Side.EAST, (current.x + current.width) - (initial.x + initial.width), true, true ); constraint.set( Side.SOUTH, (current.y + current.height) - (initial.y + initial.height), true, true ); for( Side side : Side.values() ){ hardPush( side ); } validateHardConstraints(); buildConstraints(); validateConstraints(); executeConstraints(); } /** * Gets all edges of this graph ordered by the distance of their {@link Edge#getTarget() target} node * to the root node. * @return the edges */ protected DefaultEdge[] getEdgesByDistance(){ DefaultEdge[] edges = this.edges.toArray( new DefaultEdge[ this.edges.size() ] ); Arrays.sort( edges, new Comparator<DefaultEdge>(){ public int compare( DefaultEdge o1, DefaultEdge o2 ){ return o2.getTarget().getRootDistance() - o1.getTarget().getRootDistance(); } }); return edges; } /** * Makes an initial guess telling which node has to be resized by how much. This means that the * {@link Node#getConstraints() constraints} of the nodes are set. */ protected void buildConstraints(){ DefaultEdge[] edges = getEdgesByDistance(); // fill in direct constraints for( DefaultEdge edge : edges ){ Node target = edge.getTarget(); StickMagnetGraphConstraint constraint = target.getConstraints(); Side side = edge.getSide(); StickMagnetGraphConstraint partner = edge.getSource().getConstraints(); if( partner.isSet( side )){ int delta = partner.get( side ); constraint.set( side.opposite(), delta ); if( partner.isDirect( side )){ constraint.setDirect( side.opposite(), true ); } if( !constraint.isDirect( side ) || !constraint.isSet( side )){ constraint.set( side, delta ); } } } // fill in unset constraints for( DefaultEdge edge : edges ){ StickMagnetGraphConstraint source = edge.getSource().getConstraints(); StickMagnetGraphConstraint target = edge.getTarget().getConstraints(); Side side = edge.getSide(); if( source.isSet( side ) && !target.isDirect( side.opposite() )){ target.set( side.opposite(), source.get( side )); } else if( target.isSet( side.opposite() ) && !source.isDirect( side )){ source.set( side, target.get( side.opposite() ) ); } if( !source.isSet( side )){ source.set( side, 0 ); } if( !target.isSet( side.opposite() )){ target.set( side.opposite(), 0 ); } } } /** * Starting at the root node, this method follows all {@link Edge}s going into direction <code>side</code> * and sets the hard flag on <code>side</code> and on {@link Side#opposite()} on all {@link Node#getConstraints()}s it encounters. * @param side the direction to travel */ protected void hardPush( final Side side ){ getRoot().visit( new Visitor(){ private Edge edge; public boolean beginVisit( Edge edge ){ this.edge = edge; if( edge.getSide() == side ){ return true; } if( edge.getSide() == side.opposite() ){ return false; } int source = controller.getValue( edge.getSource().getWindow(), side, true ); int target = controller.getValue( edge.getTarget().getWindow(), side, true ); return source == target; } public void endVisit( Edge edge ){ // nothing } public boolean beginVisit( Node node, boolean revisit ){ if( revisit ){ return false; } StickMagnetGraphConstraint constraint = node.getConstraints(); if( edge != null ){ if( edge.getSide() == side ){ constraint.setHard( side, true ); constraint.setHard( side.opposite(), true ); } else{ constraint.setHard( side, true ); } } return true; } public void endVisit( Node node ){ // nothing } }); } /** * Ensures that if one side of an {@link Edge} is {@link StickMagnetGraphConstraint#isHard(MagnetRequest.Side) hard}, then * the other side is hard as well. */ protected void validateHardConstraints(){ getRoot().visit( new Visitor(){ public boolean beginVisit( Edge edge ){ if( edge.getSource().getConstraints().isHard( edge.getSide() )){ edge.getTarget().getConstraints().setHard( edge.getSide().opposite(), true ); } return true; } public void endVisit( Edge edge ){ // nothing } public boolean beginVisit( Node node, boolean revisit ){ return !revisit; } public void endVisit( Node node ){ // nothing } }); } /** * Tries to ensure that the modifications described in {@link Node#getConstraints()} can be achieved. For example * a constraint resulting in a negative width or height of a {@link ScreenDockWindow} can never be achieved.<br> * The default implementation tries to smooth out resizes by distributing the changes to many windows. Note that * truly invalid boundaries will be caught and processed by the {@link BoundaryRestriction}, which cannot be * influenced by the {@link StickMagnetGraph}.<br> * Implementations should also pay attention to {@link StickMagnetGraphConstraint#isHard(MagnetRequest.Side)} and not modify * hard sides.<br> * Note that while the root window has a {@link StickMagnetGraphConstraint}, that constraint is actually ignored. */ protected void validateConstraints(){ // the nodes that were resized and need further validation final List<Node> resizedVertically = new ArrayList<StickMagnetGraph.Node>(); final List<Node> resizedHorizontally = new ArrayList<StickMagnetGraph.Node>(); getRoot().visit( new Visitor(){ public void endVisit( Edge edge ){ // ignore } public void endVisit( Node node ){ // ignore } public boolean beginVisit( Edge edge ){ return true; } public boolean beginVisit( Node node, boolean revisit ){ if( revisit ){ return false; } StickMagnetGraphConstraint constraints = node.getConstraints(); if( constraints.isSet( Side.SOUTH ) && constraints.isSet( Side.NORTH )){ if( !constraints.isHard( Side.SOUTH ) || !constraints.isHard( Side.NORTH )){ if( constraints.get( Side.SOUTH ) != constraints.get( Side.NORTH )){ resizedVertically.add( node ); } } } if( constraints.isSet( Side.EAST ) && constraints.isSet( Side.WEST )){ if( !constraints.isHard( Side.EAST ) || !constraints.isHard( Side.WEST )){ if( constraints.get( Side.EAST ) != constraints.get( Side.WEST )){ resizedHorizontally.add( node ); } } } return true; } }); for( Node node : resizedHorizontally ){ resizeRipple( node, Side.WEST ); } for( Node node : resizedVertically ){ resizeRipple( node, Side.NORTH ); } } /** * Starting from the resized node <code>node</code> this method distributes resizing over a chain of * nodes. All the affected sides are set to {@link StickMagnetGraphConstraint#setHard(MagnetRequest.Side, boolean) hard} to * prevent further modifications. Resizing stops at any {@link StickMagnetGraphConstraint#isHard(MagnetRequest.Side) hard} side. * @param node the node that was resized * @param topleft the direction into which the resize operation should ripple, the operation will also ripple in * the opposite direction */ protected void resizeRipple( Node node, Side topleft ){ int leftInitial = rippleBorder( node, topleft, true ); unmark(); int rightInitial = rippleBorder( node, topleft.opposite(), true ); unmark(); int leftAfter = rippleBorder( node, topleft, false ); unmark(); int rightAfter = rippleBorder( node, topleft.opposite(), false ); unmark(); if( leftInitial == rightInitial || leftAfter == rightAfter ){ // should not happen often, actually should not happen ever return; } rippleSide( node, topleft, leftInitial, leftAfter, rightInitial, rightAfter ); unmark(); rippleSide( node, topleft.opposite(), leftInitial, leftAfter, rightInitial, rightAfter ); unmark(); } private int rippleBorder( Node start, Side direction, boolean initial ){ start.mark(); int result = controller.getValue( start.getWindow(), direction, true ); if( !initial ){ if( start.getConstraints().isSet( direction )){ result += start.getConstraints().get( direction ); } } if( start.getConstraints().isHard( direction )){ return result; } if( direction == Side.NORTH || direction == Side.WEST ){ for( Edge edge : start.getEdges() ){ if( edge.getSource() == start && edge.getSide() == direction && !edge.getTarget().isMarked() ){ result = Math.min( result, rippleBorder( edge.getTarget(), direction, initial ) ); } else if( edge.getTarget() == start && edge.getSide() == direction.opposite() && !edge.getSource().isMarked() ){ result = Math.min( result, rippleBorder( edge.getSource(), direction, initial ) ); } } } else{ for( Edge edge : start.getEdges() ){ if( edge.getSource() == start && edge.getSide() == direction && !edge.getTarget().isMarked() ){ result = Math.max( result, rippleBorder( edge.getTarget(), direction, initial ) ); } else if( edge.getTarget() == start && edge.getSide() == direction.opposite() && !edge.getSource().isMarked() ){ result = Math.max( result, rippleBorder( edge.getSource(), direction, initial ) ); } } } return result; } private void rippleSide( Node start, Side direction, int leftInitial, int leftAfter, int rightInitial, int rightAfter ){ StickMagnetGraphConstraint constraint = start.getConstraints(); start.mark(); if( constraint.isHard( direction )){ return; } for( Edge edge : start.getEdges() ){ Node target = null; if( edge.getSource() == start && edge.getSide() == direction ){ target = edge.getTarget(); } else if( edge.getTarget() == start && edge.getSide() == direction.opposite() ){ target = edge.getSource(); } if( target != null && !target.isMarked() ){ StickMagnetGraphConstraint next = target.getConstraints(); if( next.isHard( direction.opposite() )){ return; } } } int value = controller.getValue( start.getWindow(), direction, true ); if( value <= leftInitial || value >= rightInitial ){ return; } double point = (value - leftInitial) / (double)(rightInitial - leftInitial); int actual = (int)(leftAfter + point * (rightAfter - leftAfter) + 0.5); int delta = actual - value; constraint.set( direction, delta, true, true ); for( Edge edge : start.getEdges() ){ Node target = null; if( edge.getSource() == start && edge.getSide() == direction ){ target = edge.getTarget(); } else if( edge.getTarget() == start && edge.getSide() == direction.opposite() ){ target = edge.getSource(); } if( target != null && !target.isMarked() ){ StickMagnetGraphConstraint next = target.getConstraints(); next.set( direction.opposite(), delta, true, true ); rippleSide( target, direction, leftInitial, leftAfter, rightInitial, rightAfter ); } } } /** * Reshapes all nodes except the root node according to {@link Node#getConstraints()}. */ protected void executeConstraints(){ getRoot().visit( new Visitor(){ public boolean beginVisit( Edge edge ){ return true; } public void endVisit( Edge edge ){ // nothing } public boolean beginVisit( Node node, boolean revisit ){ if( revisit ){ return false; } if( node != getRoot() ){ StickMagnetGraphConstraint constraint = node.getConstraints(); if( constraint != null ){ Rectangle initial = request.getInitialBounds( node.getWindow() ); if( constraint.isSet( Side.NORTH )){ initial.y += constraint.get( Side.NORTH ); if( constraint.isSet( Side.SOUTH )){ initial.height -= constraint.get( Side.NORTH ); initial.height += constraint.get( Side.SOUTH ); } } else if( constraint.isSet( Side.SOUTH ) ){ initial.y += constraint.get( Side.SOUTH ); } if( constraint.isSet( Side.WEST )){ initial.x += constraint.get( Side.WEST ); if( constraint.isSet( Side.EAST ) ){ initial.width -= constraint.get( Side.WEST ); initial.width += constraint.get( Side.EAST ); } } else if( constraint.isSet( Side.EAST )){ initial.x += constraint.get( Side.EAST ); } node.getWindow().setWindowBounds( initial ); } } node.getConstraints().reset(); return true; } public void endVisit( Node node ){ // ignore } }); } /** * Tells whether the side <code>side</code> of <code>window</code> will be moved if the * root window is moved at <code>side</code>. This means that there is a path from the root * window at <code>side</code> to <code>window</code> at <code>side</code> going only in * directions <code>side</code> and {@link Side#opposite()}. * @param window the window to check * @param side the side that might be affected * @return <code>true</code> if <code>side</code> is affected by the root window */ public boolean depends( final ScreenDockWindow window, final Side side ){ return depends( getRoot(), window, side ); } private boolean depends( Node node, ScreenDockWindow window, Side side ){ if( node.isMarked() ){ return false; } if( node.getWindow() == window ){ return true; } node.mark(); for( Edge edge : node.getEdges() ){ if( edge.getSide() == side || edge.getSide() == side.opposite() ){ if( edge.getSource() == node ){ if( depends( edge.getTarget(), window, side )){ node.unmark(); return true; } } else{ if( depends( edge.getSource(), window, side )){ node.unmark(); return true; } } } } node.unmark(); return false; } /** * A {@link Visitor} can be used to visit all the nodes of the graph. * @author Benjamin Sigg */ public interface Visitor{ /** * Called when <code>node</code> is added to the stack. * @param node the node that is visited * @param revisit whether this node has already been visited * @return <code>true</code> if the node should be visited, <code>false</code> if not. In the * later case {@link #endVisit(StickMagnetGraph.Edge)} is called immediately */ public boolean beginVisit( Node node, boolean revisit ); /** * Called when <code>node</code> is popped from the stack. * @param node the node that is no longer visited */ public void endVisit( Node node ); /** * Called when <code>edge</code> is added to the stack. The visitor always follows the edges from * {@link Edge#getSource() source} to {@link Edge#getTarget() target}. * @param edge the edge that is going to be visited * @return <code>true</code> if the visitor should follow the edge, <code>false</code> if not. * In the later case {@link #endVisit(StickMagnetGraph.Edge)} is called immediately */ public boolean beginVisit( Edge edge ); /** * Called when <code>edge</code> is popped from the stack. * @param edge the edge that is no longer visited */ public void endVisit( Edge edge ); } /** * Represents one node of the graph. * @author Benjamin Sigg */ public interface Node{ /** * Gest the window which is described by this node. * @return the window */ public ScreenDockWindow getWindow(); /** * Tells whether <code>window</code> is a neighbor of this node and * depends on this node, and if so tells on which side of this node * <code>window</code> lies. * @param window a window that might be a neighbor of this node * @return the side at which <code>window</code> lies or <code>null</code> */ public Side getNeighbor( ScreenDockWindow window ); /** * Gets a list of all edges that either start or end at this node. * @return the list of edges, may be empty */ public Edge[] getEdges(); /** * Visits this node and all its children. * @param visitor the visitor used to traverse the node */ public void visit( Visitor visitor ); /** * Gets the constraints telling how this node has to be modified. * @return the constraints, not <code>null</code> */ public StickMagnetGraphConstraint getConstraints(); /** * Marks this node with a flag. */ public void mark(); /** * Unmarks this node from the flag that was set by {@link #mark()}. */ public void unmark(); /** * Tells whether a flag was set by {@link #mark()}. */ public boolean isMarked(); } /** * Represents an edge between two {@link Node}s of a graph. * @author Benjamin Sigg */ public interface Edge{ /** * Gets the starting node of this edge. * @return the start */ public Node getSource(); /** * Gets the ending node of this edge. * @return the end */ public Node getTarget(); /** * Tells at which side of the {@link #getSource() starting node} this * edge leaves the node. * @return the side */ public Side getSide(); } /** * Default implementation of {@link Edge}. * @author Benjamin Sigg */ protected class DefaultEdge implements Edge{ private DefaultNode source; private DefaultNode target; private Side side; public DefaultEdge( DefaultNode source, DefaultNode target, Side side ){ this.source = source; this.target = target; this.side = side; edges.add( this ); } public DefaultNode getSource(){ return source; } public DefaultNode getTarget(){ return target; } public Side getSide(){ return side; } } /** * The default implementation of {@link Node} * @author Benjamin Sigg */ protected class DefaultNode implements Node{ private ScreenDockWindow window; private int index; private List<DefaultEdge> edges; private boolean mark = false; private StickMagnetGraphConstraint constraints = new StickMagnetGraphConstraint(); /** * Minimum distance to the root node, 0 for the root node. -1 indicates that the distance * has not yet been calculated */ private int rootDistance = -1; /** * Creates a new node. * @param index the location of this node in the array of all nodes * @param window the window represented by this node */ public DefaultNode( int index, ScreenDockWindow window ){ this.index = index; this.window = window; nodes.add( this ); } public void visit( Visitor visitor ){ try{ doVisit( visitor ); } finally{ StickMagnetGraph.this.unmark(); } } /** * Gets the constraints telling how this node has to be modified. * @return the constraints */ public StickMagnetGraphConstraint getConstraints(){ return constraints; } /** * Gets the distance of this node to the root node. * @return the distance, 0 for the root node itself */ public int getRootDistance(){ if( rootDistance == -1 ){ int min = -1; for( DefaultEdge edge : edges ){ if( edge.getTarget() == this ){ int distance = edge.getSource().getRootDistance() + 1; if( min == -1 ){ min = distance; } else{ min = Math.min( distance, min ); } } } if( min == -1 ){ min = 0; } rootDistance = min; } return rootDistance; } /** * Visits this node and all its children, sets the {@link #mark} flag. * @param visitor the visitor that is called */ public void doVisit( Visitor visitor ){ if( visitor.beginVisit( this, mark )){ mark = true; if( edges != null ){ for( DefaultEdge edge : edges ){ if( edge.getSource() == this ){ if( visitor.beginVisit( edge )){ edge.getTarget().doVisit( visitor ); visitor.endVisit( edge ); } } } } } visitor.endVisit( this ); } public void mark(){ mark = true; } public boolean isMarked(){ return mark; } public void unmark(){ mark = false; } public ScreenDockWindow getWindow(){ return window; } /** * Gets the location of this node in the array of all nodes. * @return the location */ public int getIndex(){ return index; } /** * Creates a new edge between <code>this</code> and <code>depending</code>. If there is already * an edge from either node to the other, then nothing happens. * @param side the side at which <code>depending</code> lies * @param depending the node to which a new edge may be created */ public void add( Side side, DefaultNode depending ){ if( !depending.contains( this ) && !contains( depending )){ DefaultEdge edge = new DefaultEdge( this, depending, side ); add( edge ); depending.add( edge ); } } public void add( DefaultEdge edge ){ if( edges == null ){ edges = new ArrayList<DefaultEdge>(); } edges.add( edge ); rootDistance = -1; } /** * Checks whether there is an edge from <code>this</code> node to <code>node</code>. * @param node the node to search * @return <code>true</code> if there is a directed edge */ public boolean contains( DefaultNode node ){ if( edges != null ){ for( Edge edge : edges ){ if( edge.getTarget() == node ){ return true; } } } return false; } public Side getNeighbor( ScreenDockWindow window ){ if( edges != null && getWindow() != window ){ for( Edge edge : edges ){ if( edge.getTarget().getWindow() == window ){ return edge.getSide(); } } } return null; } public DefaultEdge[] getEdges(){ if( edges == null ){ return new DefaultEdge[]{}; } else{ return edges.toArray( new DefaultEdge[ edges.size() ] ); } } } }