001/*
002 * $Id: MultiSplitLayout.java 3957 2011-03-15 18:27:26Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx;
023
024import java.awt.Component;
025import java.awt.Container;
026import java.awt.Dimension;
027import java.awt.Insets;
028import java.awt.LayoutManager;
029import java.awt.Rectangle;
030import java.beans.PropertyChangeListener;
031import java.beans.PropertyChangeSupport;
032import java.io.IOException;
033import java.io.Reader;
034import java.io.Serializable;
035import java.io.StreamTokenizer;
036import java.io.StringReader;
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Collections;
040import java.util.HashMap;
041import java.util.Iterator;
042import java.util.List;
043import java.util.ListIterator;
044import java.util.Map;
045
046import javax.swing.UIManager;
047
048
049/**
050 * The MultiSplitLayout layout manager recursively arranges its
051 * components in row and column groups called "Splits".  Elements of
052 * the layout are separated by gaps called "Dividers".  The overall
053 * layout is defined with a simple tree model whose nodes are
054 * instances of MultiSplitLayout.Split, MultiSplitLayout.Divider,
055 * and MultiSplitLayout.Leaf. Named Leaf nodes represent the space
056 * allocated to a component that was added with a constraint that
057 * matches the Leaf's name.  Extra space is distributed
058 * among row/column siblings according to their 0.0 to 1.0 weight.
059 * If no weights are specified then the last sibling always gets
060 * all of the extra space, or space reduction.
061 *
062 * <p>
063 * Although MultiSplitLayout can be used with any Container, it's
064 * the default layout manager for MultiSplitPane.  MultiSplitPane
065 * supports interactively dragging the Dividers, accessibility,
066 * and other features associated with split panes.
067 *
068 * <p>
069 * All properties in this class are bound: when a properties value
070 * is changed, all PropertyChangeListeners are fired.
071 *
072 * 
073 * @author Hans Muller 
074 * @author Luan O'Carroll
075 * @see JXMultiSplitPane
076 */
077
078/* 
079 * Changes by Luan O'Carroll 
080 * 1 Support for visibility added.
081 */
082public class MultiSplitLayout implements LayoutManager, Serializable
083{
084  public static final int DEFAULT_LAYOUT = 0;
085  public static final int NO_MIN_SIZE_LAYOUT = 1;
086  public static final int USER_MIN_SIZE_LAYOUT = 2;
087
088  private final Map<String, Component> childMap = new HashMap<String, Component>();
089  private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
090  private Node model;
091  private int dividerSize;
092  private boolean floatingDividers = true;
093
094  private boolean removeDividers = true;
095  private boolean layoutByWeight = false;
096
097  private int layoutMode;
098  private int userMinSize = 20;
099
100  /**
101   * Create a MultiSplitLayout with a default model with a single
102   * Leaf node named "default".
103   *
104   * #see setModel
105   */
106  public MultiSplitLayout()
107  {
108    this(new Leaf("default"));
109  }
110
111  /**
112   * Create a MultiSplitLayout with a default model with a single
113   * Leaf node named "default".
114   *
115   * @param layoutByWeight if true the layout is initialized in proportion to
116   * the node weights rather than the component preferred sizes.
117   * #see setModel
118   */
119  public MultiSplitLayout(boolean layoutByWeight)
120  {
121    this(new Leaf("default"));
122    this.layoutByWeight = layoutByWeight;
123  }
124  
125  /**
126   * Set the size of the child components to match the weights of the children.
127   * If the components to not all specify a weight then the available layout
128   * space is divided equally between the components.
129   */
130  public void layoutByWeight( Container parent )
131  {    
132    doLayoutByWeight( parent );
133    
134    layoutContainer( parent );
135  }
136
137  /**
138   * Set the size of the child components to match the weights of the children.
139   * If the components to not all specify a weight then the available layout
140   * space is divided equally between the components.
141   */
142  private void doLayoutByWeight( Container parent )
143  {
144    Dimension size = parent.getSize();
145    Insets insets = parent.getInsets();
146    int width = size.width - (insets.left + insets.right);
147    int height = size.height - (insets.top + insets.bottom);
148    Rectangle bounds = new Rectangle(insets.left, insets.top, width, height);
149
150    if (model instanceof Leaf)
151      model.setBounds(bounds);
152    else if (model instanceof Split)
153      doLayoutByWeight( model, bounds );
154  }
155  
156  private void doLayoutByWeight( Node node, Rectangle bounds )
157  {
158    int width = bounds.width;
159    int height = bounds.height;
160    Split split = (Split)node;
161    List<Node> splitChildren = split.getChildren();
162    double distributableWeight = 1.0;
163    int unweightedComponents = 0;
164    int dividerSpace = 0;
165    for( Node splitChild : splitChildren ) {
166      if ( !splitChild.isVisible())
167        continue;
168      else if ( splitChild instanceof Divider ) {
169        dividerSpace += dividerSize;
170        continue;
171      }
172
173      double weight = splitChild.getWeight();
174      if ( weight > 0.0 ) 
175        distributableWeight -= weight;
176      else 
177        unweightedComponents++;
178    }
179
180    if ( split.isRowLayout()) {
181      width -= dividerSpace;
182      double distributableWidth = width * distributableWeight;
183      for( Node splitChild : splitChildren ) {
184        if ( !splitChild.isVisible() || ( splitChild instanceof Divider ))
185          continue;
186
187        double weight = splitChild.getWeight();
188        Rectangle splitChildBounds = splitChild.getBounds();
189        if ( weight >= 0 ) 
190          splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, (int)( width * weight ), height );
191        else 
192          splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, (int)( distributableWidth / unweightedComponents ), height );
193
194        if ( layoutMode == USER_MIN_SIZE_LAYOUT ) {
195          splitChildBounds.setSize( Math.max( splitChildBounds.width, userMinSize ), splitChildBounds.height );
196        }
197          
198        splitChild.setBounds( splitChildBounds );
199        
200        if ( splitChild instanceof Split )
201          doLayoutByWeight( splitChild, splitChildBounds );
202        else {
203          Component comp = getComponentForNode( splitChild );
204          if ( comp != null )
205            comp.setPreferredSize( splitChildBounds.getSize());
206        }
207      }
208    }
209    else {
210      height -= dividerSpace;
211      double distributableHeight = height * distributableWeight;
212      for( Node splitChild : splitChildren ) {
213        if ( !splitChild.isVisible() || ( splitChild instanceof Divider ))
214          continue;
215
216        double weight = splitChild.getWeight();
217        Rectangle splitChildBounds = splitChild.getBounds();
218        if ( weight >= 0 ) 
219          splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, width, (int)( height * weight ));
220        else 
221          splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, width, (int)( distributableHeight / unweightedComponents ));
222
223        if ( layoutMode == USER_MIN_SIZE_LAYOUT ) {
224          splitChildBounds.setSize( splitChildBounds.width, Math.max( splitChildBounds.height, userMinSize ) );
225        }
226        
227        splitChild.setBounds( splitChildBounds );
228        
229        if ( splitChild instanceof Split )
230          doLayoutByWeight( splitChild, splitChildBounds );
231        else {
232          Component comp = getComponentForNode( splitChild );
233          if ( comp != null )
234            comp.setPreferredSize( splitChildBounds.getSize());
235        }
236      }
237    }
238  }
239  
240  /**
241   * Get the component associated with a MultiSplitLayout.Node
242   * @param n the layout node
243   * @return the component handled by the layout or null if not found
244   */
245  public Component getComponentForNode( Node n )
246  {    
247      String name = ((Leaf)n).getName();
248      return (name != null) ? (Component)childMap.get(name) : null;
249  }
250  
251  /**
252   * Get the MultiSplitLayout.Node associated with a component 
253   * @param comp the component being positioned by the layout
254   * @return the node associated with the component
255   */
256  public Node getNodeForComponent( Component comp )
257  {   
258    return getNodeForName( getNameForComponent( comp ));
259  }
260
261  /**
262   * Get the MultiSplitLayout.Node associated with a component
263   * @param name the name used to associate a component with the layout
264   * @return the node associated with the component
265   */
266  public Node getNodeForName( String name )
267  {
268    if ( model instanceof Split ) {
269      Split split = ((Split)model);
270      return getNodeForName( split, name );
271    }
272    else
273      return null;
274  }
275  
276  /**
277   * Get the name used to map a component
278   * @param child the component
279   * @return the name used to map the component or null if no mapping is found
280   */
281  public String getNameForComponent( Component child )
282  {
283    String name = null;
284    for(Map.Entry<String,Component> kv : childMap.entrySet()) {
285      if (kv.getValue() == child) {
286        name = kv.getKey();
287        break;
288      }
289    }
290    
291    return name;
292  }
293  
294  /**
295   * Get the MultiSplitLayout.Node associated with a component 
296   * @param split the layout split that owns the requested node
297   * @param comp the component being positioned by the layout
298   * @return the node associated with the component
299   */
300  public Node getNodeForComponent( Split split, Component comp )
301  {    
302    return getNodeForName( split, getNameForComponent( comp ));
303  }
304  
305  /**
306   * Get the MultiSplitLayout.Node associated with a component 
307   * @param split the layout split that owns the requested node
308   * @param name the name used to associate a component with the layout
309   * @return the node associated with the component
310   */
311  public Node getNodeForName( Split split, String name )
312  {    
313      for(Node n : split.getChildren()) {
314        if ( n instanceof Leaf ) {
315          if ( ((Leaf)n).getName().equals( name )) 
316            return n;
317        }
318        else if ( n instanceof Split ) {
319          Node n1 = getNodeForName( (Split)n, name );
320          if ( n1 != null )
321            return n1;
322        }
323      }        
324      return null;
325  }
326
327  /**
328   * Is there a valid model for the layout?
329   * @return true if there is a model
330   */
331  public boolean hasModel()
332  {
333    return model != null;
334  }
335  
336  /**
337   * Create a MultiSplitLayout with the specified model.
338   *
339   * #see setModel
340   */
341  public MultiSplitLayout(Node model) {
342    this.model = model;
343    this.dividerSize = UIManager.getInt("SplitPane.dividerSize");
344    if (this.dividerSize == 0) {
345      this.dividerSize = 7;
346    }
347  }
348  
349  public void addPropertyChangeListener(PropertyChangeListener listener) {
350    if (listener != null) {
351      pcs.addPropertyChangeListener(listener);
352    }
353  }
354  public void removePropertyChangeListener(PropertyChangeListener listener) {
355    if (listener != null) {
356      pcs.removePropertyChangeListener(listener);
357    }
358  }
359  public PropertyChangeListener[] getPropertyChangeListeners() {
360    return pcs.getPropertyChangeListeners();
361  }
362  
363  private void firePCS(String propertyName, Object oldValue, Object newValue) {
364    if (!(oldValue != null && newValue != null && oldValue.equals(newValue))) {
365      pcs.firePropertyChange(propertyName, oldValue, newValue);
366    }
367  }
368  
369  /**
370   * Return the root of the tree of Split, Leaf, and Divider nodes
371   * that define this layout.
372   *
373   * @return the value of the model property
374   * @see #setModel
375   */
376  public Node getModel() { return model; }
377  
378  /**
379   * Set the root of the tree of Split, Leaf, and Divider nodes
380   * that define this layout.  The model can be a Split node
381   * (the typical case) or a Leaf.  The default value of this
382   * property is a Leaf named "default".
383   *
384   * @param model the root of the tree of Split, Leaf, and Divider node
385   * @throws IllegalArgumentException if model is a Divider or null
386   * @see #getModel
387   */
388  public void setModel(Node model) {
389    if ((model == null) || (model instanceof Divider)) {
390      throw new IllegalArgumentException("invalid model");
391    }
392    Node oldModel = getModel();
393    this.model = model;
394    firePCS("model", oldModel, getModel());
395  }
396  
397  /**
398   * Returns the width of Dividers in Split rows, and the height of
399   * Dividers in Split columns.
400   *
401   * @return the value of the dividerSize property
402   * @see #setDividerSize
403   */
404  public int getDividerSize() { return dividerSize; }
405  
406  /**
407   * Sets the width of Dividers in Split rows, and the height of
408   * Dividers in Split columns.  The default value of this property
409   * is the same as for JSplitPane Dividers.
410   *
411   * @param dividerSize the size of dividers (pixels)
412   * @throws IllegalArgumentException if dividerSize < 0
413   * @see #getDividerSize
414   */
415  public void setDividerSize(int dividerSize) {
416    if (dividerSize < 0) {
417      throw new IllegalArgumentException("invalid dividerSize");
418    }
419    int oldDividerSize = this.dividerSize;
420    this.dividerSize = dividerSize;
421    firePCS("dividerSize", new Integer( oldDividerSize ), new Integer( dividerSize ));
422  }
423  
424  /**
425   * @return the value of the floatingDividers property
426   * @see #setFloatingDividers
427   */
428  public boolean getFloatingDividers() { return floatingDividers; }
429  
430  
431  /**
432   * If true, Leaf node bounds match the corresponding component's
433   * preferred size and Splits/Dividers are resized accordingly.
434   * If false then the Dividers define the bounds of the adjacent
435   * Split and Leaf nodes.  Typically this property is set to false
436   * after the (MultiSplitPane) user has dragged a Divider.
437   *
438   * @see #getFloatingDividers
439   */
440  public void setFloatingDividers(boolean floatingDividers)
441  {
442    boolean oldFloatingDividers = this.floatingDividers;
443    this.floatingDividers = floatingDividers;
444    firePCS("floatingDividers", new Boolean( oldFloatingDividers ), new Boolean( floatingDividers ));
445  }
446  
447  /**
448   * @return the value of the removeDividers property
449   * @see #setRemoveDividers
450   */
451  public boolean getRemoveDividers() { return removeDividers; }
452  
453  /**
454   * If true, the next divider is removed when a component is removed from the
455   * layout. If false, only the node itself is removed. Normally the next
456   * divider should be removed from the layout when a component is removed.
457   * @param removeDividers true to removed the next divider whena component is
458   * removed from teh layout
459   */
460  public void setRemoveDividers( boolean removeDividers )
461  {
462    boolean oldRemoveDividers = this.removeDividers;
463    this.removeDividers = removeDividers;
464    firePCS("removeDividers", new Boolean( oldRemoveDividers ), new Boolean( removeDividers ));
465  }
466  
467  /**
468   * Add a component to this MultiSplitLayout.  The
469   * <code>name</code> should match the name property of the Leaf
470   * node that represents the bounds of <code>child</code>.  After
471   * layoutContainer() recomputes the bounds of all of the nodes in
472   * the model, it will set this child's bounds to the bounds of the
473   * Leaf node with <code>name</code>.  Note: if a component was already
474   * added with the same name, this method does not remove it from
475   * its parent.
476   *
477   * @param name identifies the Leaf node that defines the child's bounds
478   * @param child the component to be added
479   * @see #removeLayoutComponent
480   */
481  @Override
482public void addLayoutComponent(String name, Component child) {
483    if (name == null) {
484      throw new IllegalArgumentException("name not specified");
485    }
486    childMap.put(name, child);
487  }
488  
489  /**
490   * Removes the specified component from the layout.
491   *
492   * @param child the component to be removed
493   * @see #addLayoutComponent
494   */
495  @Override
496public void removeLayoutComponent(Component child) {    
497    String name = getNameForComponent( child );
498  
499    if ( name != null ) {
500      childMap.remove( name );
501    }
502  }
503
504  /**
505   * Removes the specified node from the layout.
506   *
507   * @param name the name of the component to be removed
508   * @see #addLayoutComponent
509   */
510  public void removeLayoutNode(String name) {    
511 
512    if ( name != null ) {
513      Node n;
514      if ( !( model instanceof Split ))
515        n = model;
516      else
517        n = getNodeForName( name );
518
519      childMap.remove(name);
520
521      if ( n != null ) {
522        Split s = n.getParent();
523        s.remove( n );
524        if (removeDividers) {
525          while ( s.getChildren().size() < 2 ) {
526            Split p = s.getParent();
527            if ( p == null ) {
528              if ( s.getChildren().size() > 0 )
529              model = s.getChildren().get( 0 );
530              else 
531                model = null;
532              return;
533            }
534            if ( s.getChildren().size() == 1 ) {
535              Node next = s.getChildren().get( 0 );          
536              p.replace( s, next );
537              next.setParent( p );
538            }
539            else
540              p.remove( s );
541            s = p;
542          }
543        }
544      }
545      else {
546        childMap.remove( name );
547      }
548    }
549  }
550  
551  /**
552   * Show/Hide nodes. Any dividers that are no longer required due to one of the 
553   * nodes being made visible/invisible are also shown/hidden. The visibility of 
554   * the component managed by the node is also changed by this method
555   * @param name the node name
556   * @param visible the new node visible state
557   */
558  public void displayNode( String name, boolean visible )
559  {
560    Node node = getNodeForName( name );
561    if ( node != null ) {
562      Component comp = getComponentForNode( node );
563      comp.setVisible( visible );      
564      node.setVisible( visible );
565      
566      MultiSplitLayout.Split p = node.getParent();
567      if ( !visible ) {
568        p.hide( node );
569        if ( !p.isVisible()) 
570          p.getParent().hide( p );
571
572        p.checkDividers( p );
573        // If the split has become invisible then the parent may also have a 
574        // divider that needs to be hidden.
575        while ( !p.isVisible()) {
576          p = p.getParent();
577          if ( p != null )
578            p.checkDividers( p );
579          else
580            break;        
581        }
582      }
583      else  
584        p.restoreDividers( p );
585    }
586    setFloatingDividers( false );
587  }  
588  
589  private Component childForNode(Node node) {
590    if (node instanceof Leaf) {
591      Leaf leaf = (Leaf)node;
592      String name = leaf.getName();
593      return (name != null) ? childMap.get(name) : null;
594    }
595    return null;
596  }
597  
598  
599  private Dimension preferredComponentSize(Node node) {
600    if ( layoutMode == NO_MIN_SIZE_LAYOUT )
601      return new Dimension(0, 0);
602
603    Component child = childForNode(node);
604    return ((child != null) && child.isVisible() ) ? child.getPreferredSize() : new Dimension(0, 0);
605  }
606  
607  private Dimension minimumComponentSize(Node node) {
608    if ( layoutMode == NO_MIN_SIZE_LAYOUT )
609      return new Dimension(0, 0);
610
611    Component child = childForNode(node);
612    return ((child != null) && child.isVisible() ) ? child.getMinimumSize() : new Dimension(0, 0);
613  }
614  
615  private Dimension preferredNodeSize(Node root) {
616    if (root instanceof Leaf) {
617      return preferredComponentSize(root);
618    }
619    else if (root instanceof Divider) {
620      if ( !((Divider)root).isVisible())
621        return new Dimension(0,0);
622      int divSize = getDividerSize();
623      return new Dimension(divSize, divSize);
624    }
625    else {
626      Split split = (Split)root;
627      List<Node> splitChildren = split.getChildren();
628      int width = 0;
629      int height = 0;
630      if (split.isRowLayout()) {
631        for(Node splitChild : splitChildren) {
632          if ( !splitChild.isVisible())
633            continue;
634          Dimension size = preferredNodeSize(splitChild);
635          width += size.width;
636          height = Math.max(height, size.height);
637        }
638      }
639      else {
640            for(Node splitChild : splitChildren) {
641          if ( !splitChild.isVisible())
642            continue;
643          Dimension size = preferredNodeSize(splitChild);
644          width = Math.max(width, size.width);          
645          height += size.height;
646        }
647      }
648      return new Dimension(width, height);
649    }
650  }
651  
652  /**
653   * Get the minimum size of this node. Sums the minumum sizes of rows or 
654   * columns to get the overall minimum size for the layout node, including the 
655   * dividers.
656   * @param root the node whose size is required.
657   * @return the minimum size.
658   */
659  public Dimension minimumNodeSize(Node root) {
660    assert( root.isVisible );
661    if (root instanceof Leaf) {
662      if ( layoutMode == NO_MIN_SIZE_LAYOUT )
663        return new Dimension(0, 0);
664
665      Component child = childForNode(root);
666      return ((child != null) && child.isVisible() ) ? child.getMinimumSize() : new Dimension(0, 0);
667    }
668    else if (root instanceof Divider) {
669      if ( !((Divider)root).isVisible()  )
670        return new Dimension(0,0);
671      int divSize = getDividerSize();
672      return new Dimension(divSize, divSize);
673    }
674    else {
675      Split split = (Split)root;
676      List<Node> splitChildren = split.getChildren();
677      int width = 0;
678      int height = 0;
679      if (split.isRowLayout()) {
680            for(Node splitChild : splitChildren) {
681          if ( !splitChild.isVisible())
682            continue;
683          Dimension size = minimumNodeSize(splitChild);
684          width += size.width;
685          height = Math.max(height, size.height);
686        }
687      }
688      else {
689            for(Node splitChild : splitChildren) {
690          if ( !splitChild.isVisible())
691            continue;
692          Dimension size = minimumNodeSize(splitChild);
693          width = Math.max(width, size.width);
694          height += size.height;
695        }
696      }
697      return new Dimension(width, height);
698    }
699  }
700  
701  /**
702   * Get the maximum size of this node. Sums the minumum sizes of rows or 
703   * columns to get the overall maximum size for the layout node, including the 
704   * dividers.
705   * @param root the node whose size is required.
706   * @return the minimum size.
707   */
708  public Dimension maximumNodeSize(Node root) {
709    assert( root.isVisible );
710    if (root instanceof Leaf) {
711      Component child = childForNode(root);
712      return ((child != null) && child.isVisible() ) ? child.getMaximumSize() : new Dimension(0, 0);
713    }
714    else if (root instanceof Divider) {
715      if ( !((Divider)root).isVisible()  )
716        return new Dimension(0,0);
717      int divSize = getDividerSize();
718      return new Dimension(divSize, divSize);
719    }
720    else {
721      Split split = (Split)root;
722      List<Node> splitChildren = split.getChildren();
723      int width = Integer.MAX_VALUE;
724      int height = Integer.MAX_VALUE;
725      if (split.isRowLayout()) {
726        for(Node splitChild : splitChildren) {
727          if ( !splitChild.isVisible())
728            continue;
729          Dimension size = maximumNodeSize(splitChild);
730          width += size.width;
731          height = Math.min(height, size.height);
732        }
733      }
734      else {
735        for(Node splitChild : splitChildren) {
736          if ( !splitChild.isVisible())
737            continue;
738          Dimension size = maximumNodeSize(splitChild);
739          width = Math.min(width, size.width);
740          height += size.height;
741        }
742      }
743      return new Dimension(width, height);
744    }
745  }
746  
747  private Dimension sizeWithInsets(Container parent, Dimension size) {
748    Insets insets = parent.getInsets();
749    int width = size.width + insets.left + insets.right;
750    int height = size.height + insets.top + insets.bottom;
751    return new Dimension(width, height);
752  }
753  
754  @Override
755public Dimension preferredLayoutSize(Container parent) {
756    Dimension size = preferredNodeSize(getModel());
757    return sizeWithInsets(parent, size);
758  }
759  
760  @Override
761public Dimension minimumLayoutSize(Container parent) {
762    Dimension size = minimumNodeSize(getModel());
763    return sizeWithInsets(parent, size);
764  }
765  
766  
767  private Rectangle boundsWithYandHeight(Rectangle bounds, double y, double height) {
768    Rectangle r = new Rectangle();
769    r.setBounds((int)(bounds.getX()), (int)y, (int)(bounds.getWidth()), (int)height);
770    return r;
771  }
772  
773  private Rectangle boundsWithXandWidth(Rectangle bounds, double x, double width) {
774    Rectangle r = new Rectangle();
775    r.setBounds((int)x, (int)(bounds.getY()), (int)width, (int)(bounds.getHeight()));
776    return r;
777  }
778  
779  
780  private void minimizeSplitBounds(Split split, Rectangle bounds) {
781    assert ( split.isVisible());
782    Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0);
783    List<Node> splitChildren = split.getChildren();
784    Node lastChild = null;
785    int lastVisibleChildIdx = splitChildren.size();
786    do  {
787      lastVisibleChildIdx--;
788      lastChild = splitChildren.get( lastVisibleChildIdx );
789    } while (( lastVisibleChildIdx > 0 ) && !lastChild.isVisible());
790    
791    if ( !lastChild.isVisible())
792      return;
793    if ( lastVisibleChildIdx >= 0 ) {
794      Rectangle lastChildBounds = lastChild.getBounds();
795      if (split.isRowLayout()) {
796        int lastChildMaxX = lastChildBounds.x + lastChildBounds.width;
797        splitBounds.add(lastChildMaxX, bounds.y + bounds.height);
798      }
799      else {
800        int lastChildMaxY = lastChildBounds.y + lastChildBounds.height;
801        splitBounds.add(bounds.x + bounds.width, lastChildMaxY);
802      }
803    }
804    split.setBounds(splitBounds);
805  }
806  
807  
808  private void layoutShrink(Split split, Rectangle bounds) {
809    Rectangle splitBounds = split.getBounds();
810    ListIterator<Node> splitChildren = split.getChildren().listIterator();
811    Node lastWeightedChild = split.lastWeightedChild();
812    
813    if (split.isRowLayout()) {
814      int totalWidth = 0;          // sum of the children's widths
815      int minWeightedWidth = 0;    // sum of the weighted childrens' min widths
816      int totalWeightedWidth = 0;  // sum of the weighted childrens' widths
817        for(Node splitChild : split.getChildren()) {
818        if ( !splitChild.isVisible())
819            continue;
820        int nodeWidth = splitChild.getBounds().width;
821        int nodeMinWidth = 0;
822        if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider ))
823          nodeMinWidth = userMinSize;
824        else if ( layoutMode == DEFAULT_LAYOUT )
825          nodeMinWidth = Math.min(nodeWidth, minimumNodeSize(splitChild).width);
826        totalWidth += nodeWidth;
827        if (splitChild.getWeight() > 0.0) {
828          minWeightedWidth += nodeMinWidth;
829          totalWeightedWidth += nodeWidth;
830        }
831      }
832      
833      double x = bounds.getX();
834      double extraWidth = splitBounds.getWidth() - bounds.getWidth();
835      double availableWidth = extraWidth;
836      boolean onlyShrinkWeightedComponents =
837        (totalWeightedWidth - minWeightedWidth) > extraWidth;
838      
839      while(splitChildren.hasNext()) {
840        Node splitChild = splitChildren.next();
841        if ( !splitChild.isVisible()) { 
842          if ( splitChildren.hasNext()) 
843            splitChildren.next();
844          continue;
845        }
846        Rectangle splitChildBounds = splitChild.getBounds();
847        double minSplitChildWidth = 0.0;
848        if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider ))
849          minSplitChildWidth = userMinSize;
850        else if ( layoutMode == DEFAULT_LAYOUT )
851          minSplitChildWidth = minimumNodeSize(splitChild).getWidth();
852        double splitChildWeight = (onlyShrinkWeightedComponents)
853        ? splitChild.getWeight()
854        : (splitChildBounds.getWidth() / totalWidth);
855        
856        if (!splitChildren.hasNext()) {
857          double newWidth = Math.max(minSplitChildWidth, bounds.getMaxX() - x);
858          Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
859          layout2(splitChild, newSplitChildBounds);
860        }
861        if ( splitChild.isVisible()) {
862          if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) {
863            double oldWidth = splitChildBounds.getWidth();
864            double newWidth;
865            if ( splitChild instanceof Divider ) {
866              newWidth = dividerSize;
867            }
868            else {
869              double allocatedWidth = Math.rint(splitChildWeight * extraWidth);
870              newWidth = Math.max(minSplitChildWidth, oldWidth - allocatedWidth);
871            }
872            Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
873            layout2(splitChild, newSplitChildBounds);
874            availableWidth -= (oldWidth - splitChild.getBounds().getWidth());
875          }
876          else {
877            double existingWidth = splitChildBounds.getWidth();
878            Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth);
879            layout2(splitChild, newSplitChildBounds);
880          }
881          x = splitChild.getBounds().getMaxX();
882        }
883      }
884    }
885    
886    else {
887      int totalHeight = 0;          // sum of the children's heights
888      int minWeightedHeight = 0;    // sum of the weighted childrens' min heights
889      int totalWeightedHeight = 0;  // sum of the weighted childrens' heights
890        for(Node splitChild : split.getChildren()) {
891        if ( !splitChild.isVisible())
892          continue;
893        int nodeHeight = splitChild.getBounds().height;
894        int nodeMinHeight = 0;
895        if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider ))
896          nodeMinHeight = userMinSize;
897        else if ( layoutMode == DEFAULT_LAYOUT )
898          nodeMinHeight = Math.min(nodeHeight, minimumNodeSize(splitChild).height);
899        totalHeight += nodeHeight;
900        if (splitChild.getWeight() > 0.0) {
901          minWeightedHeight += nodeMinHeight;
902          totalWeightedHeight += nodeHeight;
903        }
904      }
905      
906      double y = bounds.getY();
907      double extraHeight = splitBounds.getHeight() - bounds.getHeight();
908      double availableHeight = extraHeight;
909      boolean onlyShrinkWeightedComponents =
910        (totalWeightedHeight - minWeightedHeight) > extraHeight;
911      
912      while(splitChildren.hasNext()) {
913        Node splitChild = splitChildren.next();
914        if ( !splitChild.isVisible()) { 
915          if ( splitChildren.hasNext()) 
916            splitChildren.next();
917          continue;
918        }
919        Rectangle splitChildBounds = splitChild.getBounds();
920        double minSplitChildHeight = 0.0;
921        if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider ))
922          minSplitChildHeight = userMinSize;
923        else if ( layoutMode == DEFAULT_LAYOUT )
924          minSplitChildHeight = minimumNodeSize(splitChild).getHeight();
925        double splitChildWeight = (onlyShrinkWeightedComponents)
926        ? splitChild.getWeight()
927        : (splitChildBounds.getHeight() / totalHeight);
928        
929        // If this split child is the last visible node it should all the 
930        // remaining space
931        if ( !hasMoreVisibleSiblings( splitChild )) {
932          double oldHeight = splitChildBounds.getHeight();
933          double newHeight;
934          if ( splitChild instanceof Divider ) {
935            newHeight = dividerSize;
936          }
937          else {
938            newHeight = Math.max(minSplitChildHeight, bounds.getMaxY() - y);
939          }
940          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
941          layout2(splitChild, newSplitChildBounds);
942          availableHeight -= (oldHeight - splitChild.getBounds().getHeight());
943        }
944        else /*if ( splitChild.isVisible()) {*/
945        if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
946          double newHeight;
947          double oldHeight = splitChildBounds.getHeight();
948          // Prevent the divider from shrinking
949          if ( splitChild instanceof Divider ) {
950            newHeight = dividerSize;
951          }
952          else {
953            double allocatedHeight = Math.rint(splitChildWeight * extraHeight);
954            newHeight = Math.max(minSplitChildHeight, oldHeight - allocatedHeight);
955          }
956          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
957          layout2(splitChild, newSplitChildBounds);
958          availableHeight -= (oldHeight - splitChild.getBounds().getHeight());
959        }
960        else {
961          double existingHeight = splitChildBounds.getHeight();
962          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight);
963          layout2(splitChild, newSplitChildBounds);
964        }
965        y = splitChild.getBounds().getMaxY();
966      }
967    }
968    
969  /* The bounds of the Split node root are set to be
970   * big enough to contain all of its children. Since
971   * Leaf children can't be reduced below their
972   * (corresponding java.awt.Component) minimum sizes,
973   * the size of the Split's bounds maybe be larger than
974   * the bounds we were asked to fit within.
975   */
976    minimizeSplitBounds(split, bounds);
977  }
978  
979  /**
980   * Check if the specified node has any following visible siblings
981   * @param splitChild the node to check
982   * @param true if there are visible children following
983   */
984  private boolean hasMoreVisibleSiblings( Node splitChild ) {
985    Node next = splitChild.nextSibling();
986    if ( next == null )
987      return false;
988    
989    do { 
990      if ( next.isVisible())
991        return true;       
992      next  = next.nextSibling();
993    } while ( next != null );
994    
995    return false;
996  }
997  
998  private void layoutGrow(Split split, Rectangle bounds) {
999    Rectangle splitBounds = split.getBounds();
1000    ListIterator<Node> splitChildren = split.getChildren().listIterator();
1001    Node lastWeightedChild = split.lastWeightedChild();
1002    
1003  /* Layout the Split's child Nodes' along the X axis.  The bounds
1004   * of each child will have the same y coordinate and height as the
1005   * layoutGrow() bounds argument.  Extra width is allocated to the
1006   * to each child with a non-zero weight:
1007   *     newWidth = currentWidth + (extraWidth * splitChild.getWeight())
1008   * Any extraWidth "left over" (that's availableWidth in the loop
1009   * below) is given to the last child.  Note that Dividers always
1010   * have a weight of zero, and they're never the last child.
1011   */
1012    if (split.isRowLayout()) {
1013      double x = bounds.getX();
1014      double extraWidth = bounds.getWidth() - splitBounds.getWidth();
1015      double availableWidth = extraWidth;
1016      
1017      while(splitChildren.hasNext()) {
1018        Node splitChild = splitChildren.next();
1019        if ( !splitChild.isVisible()) { 
1020          continue;
1021        }
1022        Rectangle splitChildBounds = splitChild.getBounds();
1023        double splitChildWeight = splitChild.getWeight();
1024        
1025        if ( !hasMoreVisibleSiblings( splitChild )) {
1026          double newWidth = bounds.getMaxX() - x;
1027          Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
1028          layout2(splitChild, newSplitChildBounds);
1029        }
1030        else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) {
1031          double allocatedWidth = (splitChild.equals(lastWeightedChild))
1032          ? availableWidth
1033            : Math.rint(splitChildWeight * extraWidth);
1034          double newWidth = splitChildBounds.getWidth() + allocatedWidth;
1035          Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
1036          layout2(splitChild, newSplitChildBounds);
1037          availableWidth -= allocatedWidth;
1038        }
1039        else {
1040          double existingWidth = splitChildBounds.getWidth();
1041          Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth);
1042          layout2(splitChild, newSplitChildBounds);
1043        }
1044        x = splitChild.getBounds().getMaxX();
1045      }
1046    }
1047    
1048  /* Layout the Split's child Nodes' along the Y axis.  The bounds
1049   * of each child will have the same x coordinate and width as the
1050   * layoutGrow() bounds argument.  Extra height is allocated to the
1051   * to each child with a non-zero weight:
1052   *     newHeight = currentHeight + (extraHeight * splitChild.getWeight())
1053   * Any extraHeight "left over" (that's availableHeight in the loop
1054   * below) is given to the last child.  Note that Dividers always
1055   * have a weight of zero, and they're never the last child.
1056   */
1057    else {
1058      double y = bounds.getY();
1059      double extraHeight = bounds.getHeight() - splitBounds.getHeight();
1060      double availableHeight = extraHeight;
1061      
1062      while(splitChildren.hasNext()) {
1063        Node splitChild = splitChildren.next();
1064        if ( !splitChild.isVisible()) { 
1065          continue;
1066        }
1067        Rectangle splitChildBounds = splitChild.getBounds();
1068        double splitChildWeight = splitChild.getWeight();
1069        
1070        if (!splitChildren.hasNext()) {
1071          double newHeight = bounds.getMaxY() - y;
1072          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
1073          layout2(splitChild, newSplitChildBounds);
1074        }
1075        else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
1076          double allocatedHeight = (splitChild.equals(lastWeightedChild))
1077          ? availableHeight
1078            : Math.rint(splitChildWeight * extraHeight);
1079          double newHeight = splitChildBounds.getHeight() + allocatedHeight;
1080          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
1081          layout2(splitChild, newSplitChildBounds);
1082          availableHeight -= allocatedHeight;
1083        }
1084        else {
1085          double existingHeight = splitChildBounds.getHeight();
1086          Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight);
1087          layout2(splitChild, newSplitChildBounds);
1088        }
1089        y = splitChild.getBounds().getMaxY();
1090      }
1091    }
1092  }
1093  
1094  
1095    /* Second pass of the layout algorithm: branch to layoutGrow/Shrink
1096     * as needed.
1097     */
1098  private void layout2(Node root, Rectangle bounds) {
1099    if (root instanceof Leaf) {
1100      Component child = childForNode(root);
1101      if (child != null) {
1102        child.setBounds(bounds);
1103      }
1104      root.setBounds(bounds);
1105    }
1106    else if (root instanceof Divider) {
1107      root.setBounds(bounds);
1108    }
1109    else if (root instanceof Split) {
1110      Split split = (Split)root;
1111      boolean grow = split.isRowLayout()
1112      ? (split.getBounds().width <= bounds.width) 
1113      : (split.getBounds().height <= bounds.height);
1114      if (grow) {
1115        layoutGrow(split, bounds);
1116        root.setBounds(bounds);
1117      }
1118      else {
1119        layoutShrink(split, bounds);
1120        // split.setBounds() called in layoutShrink()
1121      }
1122    }
1123  }
1124  
1125  
1126    /* First pass of the layout algorithm.
1127     *
1128     * If the Dividers are "floating" then set the bounds of each
1129     * node to accomodate the preferred size of all of the
1130     * Leaf's java.awt.Components.  Otherwise, just set the bounds
1131     * of each Leaf/Split node so that it's to the left of (for
1132     * Split.isRowLayout() Split children) or directly above
1133     * the Divider that follows.
1134     *
1135     * This pass sets the bounds of each Node in the layout model.  It
1136     * does not resize any of the parent Container's
1137     * (java.awt.Component) children.  That's done in the second pass,
1138     * see layoutGrow() and layoutShrink().
1139     */
1140  private void layout1(Node root, Rectangle bounds) {
1141    if (root instanceof Leaf) {
1142      root.setBounds(bounds);
1143    }
1144    else if (root instanceof Split) {
1145      Split split = (Split)root;
1146      Iterator<Node> splitChildren = split.getChildren().iterator();
1147      Rectangle childBounds = null;
1148      int divSize = getDividerSize();
1149      boolean initSplit = false;
1150      
1151      
1152      /* Layout the Split's child Nodes' along the X axis.  The bounds
1153       * of each child will have the same y coordinate and height as the
1154       * layout1() bounds argument.
1155       *
1156       * Note: the column layout code - that's the "else" clause below
1157       * this if, is identical to the X axis (rowLayout) code below.
1158       */
1159      if (split.isRowLayout()) {
1160        double x = bounds.getX();
1161        while(splitChildren.hasNext()) {
1162          Node splitChild = splitChildren.next();
1163          if ( !splitChild.isVisible()) { 
1164            if ( splitChildren.hasNext()) 
1165              splitChildren.next();
1166            continue;
1167          }
1168          Divider dividerChild =
1169            (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
1170          
1171          double childWidth = 0.0;
1172          if (getFloatingDividers()) {
1173            childWidth = preferredNodeSize(splitChild).getWidth();
1174          }
1175          else {
1176            if ((dividerChild != null) && dividerChild.isVisible()) {
1177              double cw = dividerChild.getBounds().getX() - x;
1178              if ( cw > 0.0 )
1179                childWidth = cw;
1180              else {
1181                childWidth = preferredNodeSize(splitChild).getWidth();
1182                initSplit = true;
1183              }
1184            }
1185            else {
1186              childWidth = split.getBounds().getMaxX() - x;
1187            }
1188          }
1189          childBounds = boundsWithXandWidth(bounds, x, childWidth);
1190          layout1(splitChild, childBounds);
1191          
1192          if (( initSplit || getFloatingDividers()) && (dividerChild != null) && dividerChild.isVisible()) {
1193            double dividerX = childBounds.getMaxX();
1194            Rectangle dividerBounds;
1195            dividerBounds = boundsWithXandWidth(bounds, dividerX, divSize);
1196            dividerChild.setBounds(dividerBounds);
1197          }
1198          if ((dividerChild != null) && dividerChild.isVisible()) {
1199            x = dividerChild.getBounds().getMaxX();
1200          }
1201        }
1202      }
1203      
1204      /* Layout the Split's child Nodes' along the Y axis.  The bounds
1205       * of each child will have the same x coordinate and width as the
1206       * layout1() bounds argument.  The algorithm is identical to what's
1207       * explained above, for the X axis case.
1208       */
1209      else {
1210        double y = bounds.getY();
1211        while(splitChildren.hasNext()) {
1212          Node splitChild = splitChildren.next();
1213          if ( !splitChild.isVisible()) { 
1214            if ( splitChildren.hasNext()) 
1215              splitChildren.next();
1216            continue;
1217          }
1218          Divider dividerChild =
1219            (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
1220          
1221          double childHeight = 0.0;
1222          if (getFloatingDividers()) {
1223            childHeight = preferredNodeSize(splitChild).getHeight();
1224          }
1225          else {
1226            if ((dividerChild != null) && dividerChild.isVisible()) {
1227              double cy = dividerChild.getBounds().getY() - y;
1228              if ( cy > 0.0 )
1229                childHeight = cy;
1230              else {
1231                childHeight = preferredNodeSize(splitChild).getHeight();
1232                initSplit = true;
1233              }
1234            }
1235            else {
1236              childHeight = split.getBounds().getMaxY() - y;
1237            }
1238          }
1239          childBounds = boundsWithYandHeight(bounds, y, childHeight);
1240          layout1(splitChild, childBounds);
1241          
1242          if (( initSplit || getFloatingDividers()) && (dividerChild != null) && dividerChild.isVisible()) {
1243            double dividerY = childBounds.getMaxY();
1244            Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, divSize);
1245            dividerChild.setBounds(dividerBounds);
1246          }
1247          if ((dividerChild != null) && dividerChild.isVisible()) {
1248            y = dividerChild.getBounds().getMaxY();
1249          }
1250        }
1251      }
1252      /* The bounds of the Split node root are set to be just
1253       * big enough to contain all of its children, but only
1254       * along the axis it's allocating space on.  That's
1255       * X for rows, Y for columns.  The second pass of the
1256       * layout algorithm - see layoutShrink()/layoutGrow()
1257       * allocates extra space.
1258       */
1259      minimizeSplitBounds(split, bounds);
1260    }
1261  }
1262
1263  /**
1264   * Get the layout mode
1265   * @return current layout mode
1266   */
1267  public int getLayoutMode()
1268  {
1269    return layoutMode;
1270  }
1271
1272  /**
1273   * Set the layout mode. By default this layout uses the preferred and minimum
1274   * sizes of the child components. To ignore the minimum size set the layout
1275   * mode to MultiSplitLayout.LAYOUT_NO_MIN_SIZE.
1276   * @param layoutMode the layout mode
1277   * <ul>
1278   * <li>DEFAULT_LAYOUT - use the preferred and minimum sizes when sizing the children</li>
1279   * <li>LAYOUT_NO_MIN_SIZE - ignore the minimum size when sizing the children</li>
1280   * </li>
1281   */
1282  public void setLayoutMode( int layoutMode )
1283  {
1284    this.layoutMode = layoutMode;
1285  }
1286
1287  /**
1288   * Get the minimum node size 
1289   * @return the minimum size
1290   */
1291  public int getUserMinSize()
1292  {
1293    return userMinSize;
1294  }
1295
1296  /**
1297   * Set the user defined minimum size support in the USER_MIN_SIZE_LAYOUT 
1298   * layout mode.
1299   * @param minSize the new minimum size
1300   */
1301  public void setUserMinSize( int minSize )
1302  {
1303    userMinSize = minSize;
1304  }
1305
1306  /**
1307   * Get the layoutByWeight falg. If the flag is true the layout initializes
1308   * itself using the model weights
1309   * @return the layoutByWeight
1310   */
1311  public boolean getLayoutByWeight()
1312  {
1313    return layoutByWeight;
1314  }
1315
1316  /**
1317   * Sset the layoutByWeight falg. If the flag is true the layout initializes
1318   * itself using the model weights
1319   * @param state the new layoutByWeight to set
1320   */
1321  public void setLayoutByWeight( boolean state )
1322  {
1323    layoutByWeight = state;
1324  }
1325  
1326  /**
1327   * The specified Node is either the wrong type or was configured
1328   * incorrectly.
1329   */
1330  public static class InvalidLayoutException extends RuntimeException {
1331    private final Node node;
1332    public InvalidLayoutException(String msg, Node node) {
1333      super(msg);
1334      this.node = node;
1335    }
1336    /**
1337     * @return the invalid Node.
1338     */
1339    public Node getNode() { return node; }
1340  }
1341  
1342  private void throwInvalidLayout(String msg, Node node) {
1343    throw new InvalidLayoutException(msg, node);
1344  }
1345  
1346  private void checkLayout(Node root) {
1347    if (root instanceof Split) {
1348      Split split = (Split)root;
1349      if (split.getChildren().size() <= 2) {
1350        throwInvalidLayout("Split must have > 2 children", root);
1351      }
1352      Iterator<Node> splitChildren = split.getChildren().iterator();
1353      double weight = 0.0;
1354      while(splitChildren.hasNext())       {
1355        Node splitChild = splitChildren.next();
1356        if ( !splitChild.isVisible()) { 
1357          if ( splitChildren.hasNext()) 
1358            splitChildren.next();
1359          continue;
1360        }
1361        if (splitChild instanceof Divider) {
1362          continue;
1363          //throwInvalidLayout("expected a Split or Leaf Node", splitChild);
1364        }
1365        if (splitChildren.hasNext()) {
1366          Node dividerChild = splitChildren.next();
1367          if (!(dividerChild instanceof Divider)) {
1368            throwInvalidLayout("expected a Divider Node", dividerChild);
1369          }
1370        }
1371        weight += splitChild.getWeight();
1372        checkLayout(splitChild);
1373      }
1374      if (weight > 1.0) {
1375        throwInvalidLayout("Split children's total weight > 1.0", root);
1376      }
1377    }
1378  }
1379  
1380  /**
1381   * Compute the bounds of all of the Split/Divider/Leaf Nodes in
1382   * the layout model, and then set the bounds of each child component
1383   * with a matching Leaf Node.
1384   */
1385  @Override
1386public void layoutContainer(Container parent)
1387  {
1388    if ( layoutByWeight && floatingDividers )
1389      doLayoutByWeight( parent );
1390
1391    checkLayout(getModel());
1392    Insets insets = parent.getInsets();
1393    Dimension size = parent.getSize();
1394    int width = size.width - (insets.left + insets.right);
1395    int height = size.height - (insets.top + insets.bottom);
1396    Rectangle bounds = new Rectangle( insets.left, insets.top, width, height);
1397    layout1(getModel(), bounds);
1398    layout2(getModel(), bounds);
1399  }
1400  
1401  
1402  private Divider dividerAt(Node root, int x, int y) {
1403    if (root instanceof Divider) {
1404      Divider divider = (Divider)root;
1405      return (divider.getBounds().contains(x, y)) ? divider : null;
1406    }
1407    else if (root instanceof Split) {
1408      Split split = (Split)root;
1409        for(Node child : split.getChildren()) {
1410        if ( !child.isVisible())
1411          continue;
1412        if (child.getBounds().contains(x, y)) {
1413          return dividerAt(child, x, y);
1414        }
1415      }
1416    }
1417    return null;
1418  }
1419  
1420  /**
1421   * Return the Divider whose bounds contain the specified
1422   * point, or null if there isn't one.
1423   *
1424   * @param x x coordinate
1425   * @param y y coordinate
1426   * @return the Divider at x,y
1427   */
1428  public Divider dividerAt(int x, int y) {
1429    return dividerAt(getModel(), x, y);
1430  }
1431  
1432  private boolean nodeOverlapsRectangle(Node node, Rectangle r2) {
1433    Rectangle r1 = node.getBounds();
1434    return
1435      (r1.x <= (r2.x + r2.width)) && ((r1.x + r1.width) >= r2.x) &&
1436      (r1.y <= (r2.y + r2.height)) && ((r1.y + r1.height) >= r2.y);
1437  }
1438  
1439  private List<Divider> dividersThatOverlap(Node root, Rectangle r) {
1440    if (nodeOverlapsRectangle(root, r) && (root instanceof Split)) {
1441        List<Divider> dividers = new ArrayList<Divider>();
1442        for(Node child : ((Split)root).getChildren()) {
1443        if (child instanceof Divider) {
1444          if (nodeOverlapsRectangle(child, r)) {
1445            dividers.add((Divider)child);
1446          }
1447        }
1448        else if (child instanceof Split) {
1449          dividers.addAll(dividersThatOverlap(child, r));
1450        }
1451      }
1452      return dividers;
1453    }
1454    else {
1455      return Collections.emptyList();
1456    }
1457  }
1458  
1459  /**
1460   * Return the Dividers whose bounds overlap the specified
1461   * Rectangle.
1462   *
1463   * @param r target Rectangle
1464   * @return the Dividers that overlap r
1465   * @throws IllegalArgumentException if the Rectangle is null
1466   */
1467  public List<Divider> dividersThatOverlap(Rectangle r) {
1468    if (r == null) {
1469      throw new IllegalArgumentException("null Rectangle");
1470    }
1471    return dividersThatOverlap(getModel(), r);
1472  }
1473  
1474  
1475  /**
1476   * Base class for the nodes that model a MultiSplitLayout.
1477   */
1478  public static abstract class Node implements Serializable {
1479    private Split parent = null;
1480    private Rectangle bounds = new Rectangle();
1481    private double weight = 0.0;
1482    private boolean isVisible = true;
1483    public void setVisible( boolean b ) {
1484      isVisible = b;
1485    }
1486        
1487    /**
1488     * Determines whether this node should be visible when its
1489     * parent is visible. Nodes are 
1490     * initially visible
1491     * @return <code>true</code> if the node is visible,
1492     * <code>false</code> otherwise
1493     */
1494    public boolean isVisible() {
1495      return isVisible;
1496    }
1497    
1498    /**
1499     * Returns the Split parent of this Node, or null.
1500     *
1501     * @return the value of the parent property.
1502     * @see #setParent
1503     */
1504    public Split getParent() { return parent; }
1505    
1506    /**
1507     * Set the value of this Node's parent property.  The default
1508     * value of this property is null.
1509     *
1510     * @param parent a Split or null
1511     * @see #getParent
1512     */
1513    public void setParent(Split parent) {
1514      this.parent = parent;
1515    }
1516    
1517    /**
1518     * Returns the bounding Rectangle for this Node.
1519     *
1520     * @return the value of the bounds property.
1521     * @see #setBounds
1522     */
1523    public Rectangle getBounds() {
1524      return new Rectangle(this.bounds);
1525    }
1526    
1527    /**
1528     * Set the bounding Rectangle for this node.  The value of
1529     * bounds may not be null.  The default value of bounds
1530     * is equal to <code>new Rectangle(0,0,0,0)</code>.
1531     *
1532     * @param bounds the new value of the bounds property
1533     * @throws IllegalArgumentException if bounds is null
1534     * @see #getBounds
1535     */
1536    public void setBounds(Rectangle bounds) {
1537      if (bounds == null) {
1538        throw new IllegalArgumentException("null bounds");
1539      }
1540     this.bounds = new Rectangle(bounds);
1541    }
1542    
1543    /**
1544     * Value between 0.0 and 1.0 used to compute how much space
1545     * to add to this sibling when the layout grows or how
1546     * much to reduce when the layout shrinks.
1547     *
1548     * @return the value of the weight property
1549     * @see #setWeight
1550     */
1551    public double getWeight() { return weight; }
1552    
1553    /**
1554     * The weight property is a between 0.0 and 1.0 used to
1555     * compute how much space to add to this sibling when the
1556     * layout grows or how much to reduce when the layout shrinks.
1557     * If rowLayout is true then this node's width grows
1558     * or shrinks by (extraSpace * weight).  If rowLayout is false,
1559     * then the node's height is changed.  The default value
1560     * of weight is 0.0.
1561     *
1562     * @param weight a double between 0.0 and 1.0
1563     * @see #getWeight
1564     * @see MultiSplitLayout#layoutContainer
1565     * @throws IllegalArgumentException if weight is not between 0.0 and 1.0
1566     */
1567    public void setWeight(double weight) {
1568      if ((weight < 0.0)|| (weight > 1.0)) {
1569        throw new IllegalArgumentException("invalid weight");
1570      }
1571      this.weight = weight;
1572    }
1573    
1574    private Node siblingAtOffset(int offset) {
1575      Split p = getParent();
1576      if (p == null) { return null; }
1577      List<Node> siblings = p.getChildren();
1578      int index = siblings.indexOf(this);
1579      if (index == -1) { return null; }
1580      index += offset;
1581      return ((index > -1) && (index < siblings.size())) ? siblings.get(index) : null;
1582    }
1583    
1584    /**
1585     * Return the Node that comes after this one in the parent's
1586     * list of children, or null.  If this node's parent is null,
1587     * or if it's the last child, then return null.
1588     *
1589     * @return the Node that comes after this one in the parent's list of children.
1590     * @see #previousSibling
1591     * @see #getParent
1592     */
1593    public Node nextSibling() {
1594      return siblingAtOffset(+1);
1595    }
1596    
1597    /**
1598     * Return the Node that comes before this one in the parent's
1599     * list of children, or null.  If this node's parent is null,
1600     * or if it's the last child, then return null.
1601     *
1602     * @return the Node that comes before this one in the parent's list of children.
1603     * @see #nextSibling
1604     * @see #getParent
1605     */
1606    public Node previousSibling() {
1607      return siblingAtOffset(-1);
1608    }
1609  }
1610
1611  public static class RowSplit extends Split {
1612    public RowSplit() {
1613    }
1614
1615    public RowSplit(Node... children) {
1616        setChildren(children);
1617    }
1618    
1619    /**
1620     * Returns true if the this Split's children are to be 
1621     * laid out in a row: all the same height, left edge
1622     * equal to the previous Node's right edge.  If false,
1623     * children are laid on in a column.
1624     * 
1625     * @return the value of the rowLayout property.
1626     * @see #setRowLayout
1627     */
1628    @Override
1629    public final boolean isRowLayout() { return true; }
1630  }
1631    
1632  public static class ColSplit extends Split {
1633    public ColSplit() {
1634    }
1635
1636    public ColSplit(Node... children) {
1637        setChildren(children);
1638    }
1639    
1640    /**
1641     * Returns true if the this Split's children are to be 
1642     * laid out in a row: all the same height, left edge
1643     * equal to the previous Node's right edge.  If false,
1644     * children are laid on in a column.
1645     * 
1646     * @return the value of the rowLayout property.
1647     * @see #setRowLayout
1648     */
1649    @Override
1650    public final boolean isRowLayout() { return false; }
1651  }
1652
1653  /**
1654   * Defines a vertical or horizontal subdivision into two or more
1655   * tiles.
1656   */
1657  public static class Split extends Node {
1658    private List<Node> children = Collections.emptyList();
1659    private boolean rowLayout = true;
1660    private String name;
1661
1662    public Split(Node... children) {
1663      setChildren(children);
1664    }
1665    
1666    /**
1667     * Default constructor to support xml (de)serialization and other bean spec dependent ops.
1668     * Resulting instance of Split is invalid until setChildren() is called.
1669     */
1670    public Split() {
1671    }
1672
1673    /**
1674     * Determines whether this node should be visible when its
1675     * parent is visible. Nodes are 
1676     * initially visible
1677     * @return <code>true</code> if the node is visible,
1678     * <code>false</code> otherwise
1679     */
1680    @Override
1681    public boolean isVisible() {
1682        for(Node child : children) {
1683        if ( child.isVisible() && !( child instanceof Divider ))
1684          return true;
1685      }
1686      return false;
1687    }
1688    
1689    /**
1690     * Returns true if the this Split's children are to be
1691     * laid out in a row: all the same height, left edge
1692     * equal to the previous Node's right edge.  If false,
1693     * children are laid on in a column.
1694     *
1695     * @return the value of the rowLayout property.
1696     * @see #setRowLayout
1697     */
1698    public boolean isRowLayout() { return rowLayout; }
1699    
1700    /**
1701     * Set the rowLayout property.  If true, all of this Split's
1702     * children are to be laid out in a row: all the same height,
1703     * each node's left edge equal to the previous Node's right
1704     * edge.  If false, children are laid on in a column.  Default
1705     * value is true.
1706     *
1707     * @param rowLayout true for horizontal row layout, false for column
1708     * @see #isRowLayout
1709     */
1710    public void setRowLayout(boolean rowLayout) {
1711      this.rowLayout = rowLayout;
1712    }
1713    
1714    /**
1715     * Returns this Split node's children.  The returned value
1716     * is not a reference to the Split's internal list of children
1717     *
1718     * @return the value of the children property.
1719     * @see #setChildren
1720     */
1721    public List<Node> getChildren() {
1722      return new ArrayList<Node>(children);
1723    }
1724    
1725    
1726    /**
1727     * Remove a node from the layout. Any sibling dividers will also be removed
1728     * @param n the node to be removed
1729     */
1730    public void remove( Node n ) { 
1731      if ( n.nextSibling() instanceof Divider )
1732        children.remove( n.nextSibling() );
1733      else if ( n.previousSibling() instanceof Divider )
1734        children.remove( n.previousSibling() );
1735      children.remove( n );
1736    }
1737    
1738    /**
1739     * Replace one node with another. This method is used when a child is removed 
1740     * from a split and the split is no longer required, in which case the 
1741     * remaining node in the child split can replace the split in the parent 
1742     * node
1743     * @param target the node being replaced
1744     * @param replacement the replacement node
1745     */
1746    public void replace( Node target, Node replacement ) { 
1747      int idx = children.indexOf( target );
1748      children.remove( target );
1749      children.add( idx, replacement );
1750
1751      replacement.setParent ( this );
1752      target.setParent( this );
1753    }
1754    
1755    /**
1756     * Change a node to being hidden. Any associated divider nodes are also hidden
1757     * @param target the node to hide
1758     */
1759    public void hide( Node target ){ 
1760      Node next = target.nextSibling();
1761      if ( next instanceof Divider )
1762        next.setVisible( false );
1763      else {
1764        Node prev = target.previousSibling();
1765        if ( prev instanceof Divider )
1766          prev.setVisible( false );
1767      }
1768      target.setVisible( false );
1769    }
1770    
1771    /**
1772     * Check the dividers to ensure that redundant dividers are hidden and do
1773     * not interfere in the layout, for example when all the children of a split 
1774     * are hidden (the split is then invisible), so two dividers may otherwise
1775     * appear next to one another.
1776     * @param split the split to check
1777     */
1778    public void checkDividers( Split split ) { 
1779      ListIterator<Node> splitChildren = split.getChildren().listIterator();
1780      while( splitChildren.hasNext()) {
1781        Node splitChild = splitChildren.next();
1782        if ( !splitChild.isVisible()) {
1783          continue;
1784        }
1785        else if ( splitChildren.hasNext()) {
1786          Node dividerChild = splitChildren.next();
1787          if ( dividerChild instanceof Divider ) {
1788            if ( splitChildren.hasNext()) {
1789              Node rightChild = splitChildren.next();
1790              while ( !rightChild.isVisible()) {
1791                rightChild = rightChild.nextSibling();
1792                if ( rightChild == null ) {
1793                  // No visible right sibling found, so hide the divider
1794                  dividerChild.setVisible( false );
1795                  break;
1796                }
1797              }
1798
1799              // A visible child is found but it's a divider and therefore 
1800              // we have to visible and adjacent dividers - so we hide one
1801              if (( rightChild != null ) && ( rightChild instanceof Divider ))
1802                dividerChild.setVisible( false );
1803            }
1804          }
1805          else if (( splitChild instanceof Divider ) && ( dividerChild instanceof Divider )) {
1806            splitChild.setVisible( false );
1807          }
1808        }
1809      }
1810    }
1811    
1812    /**
1813     * Restore any of the hidden dividers that are required to separate visible nodes
1814     * @param split the node to check
1815     */
1816    public void restoreDividers( Split split ) { 
1817      boolean nextDividerVisible = false;
1818      ListIterator<Node> splitChildren = split.getChildren().listIterator();
1819      while( splitChildren.hasNext()) {
1820        Node splitChild = splitChildren.next();
1821        if ( splitChild instanceof Divider ) {
1822          Node prev = splitChild.previousSibling();
1823          if ( prev.isVisible()) { 
1824            Node next = splitChild.nextSibling();
1825            while ( next != null ) {
1826              if ( next.isVisible()) {
1827                splitChild.setVisible( true );
1828                break;
1829              }
1830              next = next.nextSibling();
1831            }
1832          }
1833        }
1834      }
1835      if ( split.getParent() != null )
1836        restoreDividers( split.getParent());
1837    }
1838    
1839    /**
1840     * Set's the children property of this Split node.  The parent
1841     * of each new child is set to this Split node, and the parent
1842     * of each old child (if any) is set to null.  This method
1843     * defensively copies the incoming List.  Default value is
1844     * an empty List.
1845     *
1846     * @param children List of children
1847     * @see #getChildren
1848     * @throws IllegalArgumentException if children is null
1849     */
1850    public void setChildren(List<Node> children) {
1851      if (children == null) {
1852        throw new IllegalArgumentException("children must be a non-null List");
1853      }
1854        for(Node child : this.children) {
1855        child.setParent(null);
1856      }
1857
1858      this.children = new ArrayList<Node>(children);
1859        for(Node child : this.children) {
1860        child.setParent(this);
1861      }
1862    }
1863       
1864    /**
1865     * Convenience method for setting the children of this Split node.  The parent
1866     * of each new child is set to this Split node, and the parent
1867     * of each old child (if any) is set to null.  This method
1868     * defensively copies the incoming array.
1869     * 
1870     * @param children array of children
1871     * @see #getChildren
1872     * @throws IllegalArgumentException if children is null
1873     */
1874    public void setChildren(Node... children) {
1875        setChildren(children == null ? null : Arrays.asList(children));
1876    }
1877        
1878    /**
1879     * Convenience method that returns the last child whose weight
1880     * is > 0.0.
1881     *
1882     * @return the last child whose weight is > 0.0.
1883     * @see #getChildren
1884     * @see Node#getWeight
1885     */
1886    public final Node lastWeightedChild() {
1887      List<Node> kids = getChildren();
1888      Node weightedChild = null;
1889        for(Node child : kids) {
1890        if ( !child.isVisible())
1891          continue;
1892        if (child.getWeight() > 0.0) {
1893          weightedChild = child;
1894        }
1895      }
1896      return weightedChild;
1897    }
1898    
1899    /**
1900     * Return the Leaf's name.
1901     *
1902     * @return the value of the name property.
1903     * @see #setName
1904     */
1905    public String getName() { return name; }
1906    
1907    /**
1908     * Set the value of the name property.  Name may not be null.
1909     *
1910     * @param name value of the name property
1911     * @throws IllegalArgumentException if name is null
1912     */
1913    public void setName(String name) {
1914      if (name == null) {
1915        throw new IllegalArgumentException("name is null");
1916      }
1917      this.name = name;
1918    }
1919        
1920    @Override
1921    public String toString() {
1922      int nChildren = getChildren().size();
1923      StringBuffer sb = new StringBuffer("MultiSplitLayout.Split");
1924      sb.append(" \"");
1925      sb.append(getName());
1926      sb.append("\"");
1927      sb.append(isRowLayout() ? " ROW [" : " COLUMN [");
1928      sb.append(nChildren + ((nChildren == 1) ? " child" : " children"));
1929      sb.append("] ");
1930      sb.append(getBounds());
1931      return sb.toString();
1932    }
1933  }
1934  
1935  
1936  /**
1937   * Models a java.awt Component child.
1938   */
1939  public static class Leaf extends Node {
1940    private String name = "";
1941    
1942    /**
1943     * Create a Leaf node.  The default value of name is "".
1944     */
1945    public Leaf() { }
1946    
1947  
1948    /**
1949     * Create a Leaf node with the specified name.  Name can not
1950     * be null.
1951     *
1952     * @param name value of the Leaf's name property
1953     * @throws IllegalArgumentException if name is null
1954     */
1955    public Leaf(String name) {
1956      if (name == null) {
1957        throw new IllegalArgumentException("name is null");
1958      }
1959      this.name = name;
1960    }
1961    
1962    /**
1963     * Return the Leaf's name.
1964     *
1965     * @return the value of the name property.
1966     * @see #setName
1967     */
1968    public String getName() { return name; }
1969    
1970    /**
1971     * Set the value of the name property.  Name may not be null.
1972     *
1973     * @param name value of the name property
1974     * @throws IllegalArgumentException if name is null
1975     */
1976    public void setName(String name) {
1977      if (name == null) {
1978        throw new IllegalArgumentException("name is null");
1979      }
1980      this.name = name;
1981    }
1982    
1983    @Override
1984    public String toString() {
1985      StringBuffer sb = new StringBuffer("MultiSplitLayout.Leaf");
1986      sb.append(" \"");
1987      sb.append(getName());
1988      sb.append("\"");
1989      sb.append(" weight=");
1990      sb.append(getWeight());
1991      sb.append(" ");
1992      sb.append(getBounds());
1993      return sb.toString();
1994    }
1995  }
1996  
1997  
1998  /**
1999   * Models a single vertical/horiztonal divider.
2000   */
2001  public static class Divider extends Node {   
2002    /**
2003     * Convenience method, returns true if the Divider's parent
2004     * is a Split row (a Split with isRowLayout() true), false
2005     * otherwise. In other words if this Divider's major axis
2006     * is vertical, return true.
2007     *
2008     * @return true if this Divider is part of a Split row.
2009     */
2010    public final boolean isVertical() {
2011      Split parent = getParent();
2012      return (parent != null) ? parent.isRowLayout() : false;
2013    }
2014    
2015    /**
2016     * Dividers can't have a weight, they don't grow or shrink.
2017     * @throws UnsupportedOperationException
2018     */
2019    @Override
2020    public void setWeight(double weight) {
2021      throw new UnsupportedOperationException();
2022    }
2023    
2024    @Override
2025    public String toString() {
2026      return "MultiSplitLayout.Divider " + getBounds().toString();
2027    }
2028  }
2029  
2030  
2031  private static void throwParseException(StreamTokenizer st, String msg) throws Exception {
2032    throw new Exception("MultiSplitLayout.parseModel Error: " + msg);
2033  }
2034  
2035  private static void parseAttribute(String name, StreamTokenizer st, Node node) throws Exception {
2036    if ((st.nextToken() != '=')) {
2037      throwParseException(st, "expected '=' after " + name);
2038    }
2039    if (name.equalsIgnoreCase("WEIGHT")) {
2040      if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
2041        node.setWeight(st.nval);
2042      }
2043      else {
2044        throwParseException(st, "invalid weight");
2045      }
2046    }
2047    else if (name.equalsIgnoreCase("NAME")) {
2048      if (st.nextToken() == StreamTokenizer.TT_WORD) {
2049        if (node instanceof Leaf) {
2050          ((Leaf)node).setName(st.sval);
2051        }
2052        else if (node instanceof Split) {
2053          ((Split)node).setName(st.sval);
2054        }
2055        else {
2056          throwParseException(st, "can't specify name for " + node);
2057        }
2058      }
2059      else {
2060        throwParseException(st, "invalid name");
2061      }
2062    }
2063    else {
2064      throwParseException(st, "unrecognized attribute \"" + name + "\"");
2065    }
2066  }
2067  
2068  private static void addSplitChild(Split parent, Node child) {
2069    List<Node> children = new ArrayList<Node>(parent.getChildren());
2070    if (children.size() == 0) {
2071      children.add(child);
2072    }
2073    else {
2074      children.add(new Divider());
2075      children.add(child);
2076    }
2077    parent.setChildren(children);
2078  }
2079  
2080  private static void parseLeaf(StreamTokenizer st, Split parent) throws Exception {
2081    Leaf leaf = new Leaf();
2082    int token;
2083    while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
2084      if (token == ')') {
2085        break;
2086      }
2087      if (token == StreamTokenizer.TT_WORD) {
2088        parseAttribute(st.sval, st, leaf);
2089      }
2090      else {
2091        throwParseException(st, "Bad Leaf: " + leaf);
2092      }
2093    }
2094    addSplitChild(parent, leaf);
2095  }
2096  
2097  private static void parseSplit(StreamTokenizer st, Split parent) throws Exception {
2098    int token;
2099    while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
2100      if (token == ')') {
2101        break;
2102      }
2103      else if (token == StreamTokenizer.TT_WORD) {
2104        if (st.sval.equalsIgnoreCase("WEIGHT")) {
2105          parseAttribute(st.sval, st, parent);
2106        }
2107        else if (st.sval.equalsIgnoreCase("NAME")) {
2108          parseAttribute(st.sval, st, parent);
2109        }
2110        else {
2111          addSplitChild(parent, new Leaf(st.sval));
2112        }
2113      }
2114      else if (token == '(') {
2115        if ((token = st.nextToken()) != StreamTokenizer.TT_WORD) {
2116          throwParseException(st, "invalid node type");
2117        }
2118        String nodeType = st.sval.toUpperCase();
2119        if (nodeType.equals("LEAF")) {
2120          parseLeaf(st, parent);
2121        }
2122        else if (nodeType.equals("ROW") || nodeType.equals("COLUMN")) {
2123          Split split = new Split();
2124          split.setRowLayout(nodeType.equals("ROW"));
2125          addSplitChild(parent, split);
2126          parseSplit(st, split);
2127        }
2128        else {
2129          throwParseException(st, "unrecognized node type '" + nodeType + "'");
2130        }
2131      }
2132    }
2133  }
2134  
2135  private static Node parseModel(Reader r) {
2136    StreamTokenizer st = new StreamTokenizer(r);
2137    try {
2138      Split root = new Split();
2139      parseSplit(st, root);
2140      return root.getChildren().get(0);
2141    }
2142    catch (Exception e) {
2143      System.err.println(e);
2144    }
2145    finally {
2146      try { r.close(); } catch (IOException ignore) {}
2147    }
2148    return null;
2149  }
2150  
2151  /**
2152   * A convenience method that converts a string to a
2153   * MultiSplitLayout model (a tree of Nodes) using a
2154   * a simple syntax.  Nodes are represented by
2155   * parenthetical expressions whose first token
2156   * is one of ROW/COLUMN/LEAF.  ROW and COLUMN specify
2157   * horizontal and vertical Split nodes respectively,
2158   * LEAF specifies a Leaf node.  A Leaf's name and
2159   * weight can be specified with attributes,
2160   * name=<i>myLeafName</i> weight=<i>myLeafWeight</i>.
2161   * Similarly, a Split's weight can be specified with
2162   * weight=<i>mySplitWeight</i>.
2163   *
2164   * <p> For example, the following expression generates
2165   * a horizontal Split node with three children:
2166   * the Leafs named left and right, and a Divider in
2167   * between:
2168   * <pre>
2169   * (ROW (LEAF name=left) (LEAF name=right weight=1.0))
2170   * </pre>
2171   *
2172   * <p> Dividers should not be included in the string,
2173   * they're added automatcially as needed.  Because
2174   * Leaf nodes often only need to specify a name, one
2175   * can specify a Leaf by just providing the name.
2176   * The previous example can be written like this:
2177   * <pre>
2178   * (ROW left (LEAF name=right weight=1.0))
2179   * </pre>
2180   *
2181   * <p>Here's a more complex example.  One row with
2182   * three elements, the first and last of which are columns
2183   * with two leaves each:
2184   * <pre>
2185   * (ROW (COLUMN weight=0.5 left.top left.bottom)
2186   *      (LEAF name=middle)
2187   *      (COLUMN weight=0.5 right.top right.bottom))
2188   * </pre>
2189   *
2190   *
2191   * <p> This syntax is not intended for archiving or
2192   * configuration files .  It's just a convenience for
2193   * examples and tests.
2194   *
2195   * @return the Node root of a tree based on s.
2196   */
2197  public static Node parseModel(String s) {
2198    return parseModel(new StringReader(s));
2199  }
2200  
2201  
2202  private static void printModel(String indent, Node root) {
2203    if (root instanceof Split) {
2204      Split split = (Split)root;
2205      System.out.println(indent + split);
2206        for(Node child : split.getChildren()) {
2207        printModel(indent + "  ", child);
2208      }
2209    }
2210    else {
2211      System.out.println(indent + root);
2212    }
2213  }
2214  
2215  /**
2216   * Print the tree with enough detail for simple debugging.
2217   */
2218  public static void printModel(Node root) {
2219    printModel("", root);
2220  }
2221}