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 }