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