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 }