/* * $Id: MultiSplitLayout.java 3471 2009-08-27 13:10:39Z kleopatra $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Rectangle; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; import java.io.Reader; import java.io.StreamTokenizer; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import javax.swing.UIManager; /** * The MultiSplitLayout layout manager recursively arranges its * components in row and column groups called "Splits". Elements of * the layout are separated by gaps called "Dividers". The overall * layout is defined with a simple tree model whose nodes are * instances of MultiSplitLayout.Split, MultiSplitLayout.Divider, * and MultiSplitLayout.Leaf. Named Leaf nodes represent the space * allocated to a component that was added with a constraint that * matches the Leaf's name. Extra space is distributed * among row/column siblings according to their 0.0 to 1.0 weight. * If no weights are specified then the last sibling always gets * all of the extra space, or space reduction. * *

* Although MultiSplitLayout can be used with any Container, it's * the default layout manager for MultiSplitPane. MultiSplitPane * supports interactively dragging the Dividers, accessibility, * and other features associated with split panes. * *

* All properties in this class are bound: when a properties value * is changed, all PropertyChangeListeners are fired. * * * @author Hans Muller * @author Luan O'Carroll * @see JXMultiSplitPane */ /* * Changes by Luan O'Carroll * 1 Support for visibility added. */ public class MultiSplitLayout implements LayoutManager { public static final int DEFAULT_LAYOUT = 0; public static final int NO_MIN_SIZE_LAYOUT = 1; public static final int USER_MIN_SIZE_LAYOUT = 2; private final Map childMap = new HashMap(); private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private Node model; private int dividerSize; private boolean floatingDividers = true; private boolean removeDividers = true; private boolean layoutByWeight = false; private int layoutMode; private int userMinSize = 20; /** * Create a MultiSplitLayout with a default model with a single * Leaf node named "default". * * #see setModel */ public MultiSplitLayout() { this(new Leaf("default")); } /** * Create a MultiSplitLayout with a default model with a single * Leaf node named "default". * * @param layoutByWeight if true the layout is initialized in proportion to * the node weights rather than the component preferred sizes. * #see setModel */ public MultiSplitLayout(boolean layoutByWeight) { this(new Leaf("default")); this.layoutByWeight = layoutByWeight; } /** * Set the size of the child components to match the weights of the children. * If the components to not all specify a weight then the available layout * space is divided equally between the components. */ public void layoutByWeight( Container parent ) { doLayoutByWeight( parent ); layoutContainer( parent ); } /** * Set the size of the child components to match the weights of the children. * If the components to not all specify a weight then the available layout * space is divided equally between the components. */ private void doLayoutByWeight( Container parent ) { Dimension size = parent.getSize(); Insets insets = parent.getInsets(); int width = size.width - (insets.left + insets.right); int height = size.height - (insets.top + insets.bottom); Rectangle bounds = new Rectangle(insets.left, insets.top, width, height); if (model instanceof Leaf) model.setBounds(bounds); else if (model instanceof Split) doLayoutByWeight( model, bounds ); } private void doLayoutByWeight( Node node, Rectangle bounds ) { int width = bounds.width; int height = bounds.height; Split split = (Split)node; List splitChildren = split.getChildren(); double distributableWeight = 1.0; int unweightedComponents = 0; int dividerSpace = 0; for( Node splitChild : splitChildren ) { if ( !splitChild.isVisible()) continue; else if ( splitChild instanceof Divider ) { dividerSpace += dividerSize; continue; } double weight = splitChild.getWeight(); if ( weight > 0.0 ) distributableWeight -= weight; else unweightedComponents++; } if ( split.isRowLayout()) { width -= dividerSpace; double distributableWidth = width * distributableWeight; for( Node splitChild : splitChildren ) { if ( !splitChild.isVisible() || ( splitChild instanceof Divider )) continue; double weight = splitChild.getWeight(); Rectangle splitChildBounds = splitChild.getBounds(); if ( weight >= 0 ) splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, (int)( width * weight ), height ); else splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, (int)( distributableWidth / unweightedComponents ), height ); if ( layoutMode == USER_MIN_SIZE_LAYOUT ) { splitChildBounds.setSize( Math.max( splitChildBounds.width, userMinSize ), splitChildBounds.height ); } splitChild.setBounds( splitChildBounds ); if ( splitChild instanceof Split ) doLayoutByWeight( splitChild, splitChildBounds ); else { Component comp = getComponentForNode( splitChild ); if ( comp != null ) comp.setPreferredSize( splitChildBounds.getSize()); } } } else { height -= dividerSpace; double distributableHeight = height * distributableWeight; for( Node splitChild : splitChildren ) { if ( !splitChild.isVisible() || ( splitChild instanceof Divider )) continue; double weight = splitChild.getWeight(); Rectangle splitChildBounds = splitChild.getBounds(); if ( weight >= 0 ) splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, width, (int)( height * weight )); else splitChildBounds = new Rectangle( splitChildBounds.x, splitChildBounds.y, width, (int)( distributableHeight / unweightedComponents )); if ( layoutMode == USER_MIN_SIZE_LAYOUT ) { splitChildBounds.setSize( splitChildBounds.width, Math.max( splitChildBounds.height, userMinSize ) ); } splitChild.setBounds( splitChildBounds ); if ( splitChild instanceof Split ) doLayoutByWeight( splitChild, splitChildBounds ); else { Component comp = getComponentForNode( splitChild ); if ( comp != null ) comp.setPreferredSize( splitChildBounds.getSize()); } } } } /** * Get the component associated with a MultiSplitLayout.Node * @param n the layout node * @return the component handled by the layout or null if not found */ public Component getComponentForNode( Node n ) { String name = ((Leaf)n).getName(); return (name != null) ? (Component)childMap.get(name) : null; } /** * Get the MultiSplitLayout.Node associated with a component * @param comp the component being positioned by the layout * @return the node associated with the component */ public Node getNodeForComponent( Component comp ) { return getNodeForName( getNameForComponent( comp )); } /** * Get the MultiSplitLayout.Node associated with a component * @param name the name used to associate a component with the layout * @return the node associated with the component */ public Node getNodeForName( String name ) { if ( model instanceof Split ) { Split split = ((Split)model); return getNodeForName( split, name ); } else return null; } /** * Get the name used to map a component * @param child the component * @return the name used to map the component or null if no mapping is found */ public String getNameForComponent( Component child ) { String name = null; for(Map.Entry kv : childMap.entrySet()) { if (kv.getValue() == child) { name = kv.getKey(); break; } } return name; } /** * Get the MultiSplitLayout.Node associated with a component * @param split the layout split that owns the requested node * @param comp the component being positioned by the layout * @return the node associated with the component */ public Node getNodeForComponent( Split split, Component comp ) { return getNodeForName( split, getNameForComponent( comp )); } /** * Get the MultiSplitLayout.Node associated with a component * @param split the layout split that owns the requested node * @param name the name used to associate a component with the layout * @return the node associated with the component */ public Node getNodeForName( Split split, String name ) { for(Node n : split.getChildren()) { if ( n instanceof Leaf ) { if ( ((Leaf)n).getName().equals( name )) return n; } else if ( n instanceof Split ) { Node n1 = getNodeForName( (Split)n, name ); if ( n1 != null ) return n1; } } return null; } /** * Is there a valid model for the layout? * @return true if there is a model */ public boolean hasModel() { return model != null; } /** * Create a MultiSplitLayout with the specified model. * * #see setModel */ public MultiSplitLayout(Node model) { this.model = model; this.dividerSize = UIManager.getInt("SplitPane.dividerSize"); if (this.dividerSize == 0) { this.dividerSize = 7; } } public void addPropertyChangeListener(PropertyChangeListener listener) { if (listener != null) { pcs.addPropertyChangeListener(listener); } } public void removePropertyChangeListener(PropertyChangeListener listener) { if (listener != null) { pcs.removePropertyChangeListener(listener); } } public PropertyChangeListener[] getPropertyChangeListeners() { return pcs.getPropertyChangeListeners(); } private void firePCS(String propertyName, Object oldValue, Object newValue) { if (!(oldValue != null && newValue != null && oldValue.equals(newValue))) { pcs.firePropertyChange(propertyName, oldValue, newValue); } } /** * Return the root of the tree of Split, Leaf, and Divider nodes * that define this layout. * * @return the value of the model property * @see #setModel */ public Node getModel() { return model; } /** * Set the root of the tree of Split, Leaf, and Divider nodes * that define this layout. The model can be a Split node * (the typical case) or a Leaf. The default value of this * property is a Leaf named "default". * * @param model the root of the tree of Split, Leaf, and Divider node * @throws IllegalArgumentException if model is a Divider or null * @see #getModel */ public void setModel(Node model) { if ((model == null) || (model instanceof Divider)) { throw new IllegalArgumentException("invalid model"); } Node oldModel = getModel(); this.model = model; firePCS("model", oldModel, getModel()); } /** * Returns the width of Dividers in Split rows, and the height of * Dividers in Split columns. * * @return the value of the dividerSize property * @see #setDividerSize */ public int getDividerSize() { return dividerSize; } /** * Sets the width of Dividers in Split rows, and the height of * Dividers in Split columns. The default value of this property * is the same as for JSplitPane Dividers. * * @param dividerSize the size of dividers (pixels) * @throws IllegalArgumentException if dividerSize < 0 * @see #getDividerSize */ public void setDividerSize(int dividerSize) { if (dividerSize < 0) { throw new IllegalArgumentException("invalid dividerSize"); } int oldDividerSize = this.dividerSize; this.dividerSize = dividerSize; firePCS("dividerSize", new Integer( oldDividerSize ), new Integer( dividerSize )); } /** * @return the value of the floatingDividers property * @see #setFloatingDividers */ public boolean getFloatingDividers() { return floatingDividers; } /** * If true, Leaf node bounds match the corresponding component's * preferred size and Splits/Dividers are resized accordingly. * If false then the Dividers define the bounds of the adjacent * Split and Leaf nodes. Typically this property is set to false * after the (MultiSplitPane) user has dragged a Divider. * * @see #getFloatingDividers */ public void setFloatingDividers(boolean floatingDividers) { boolean oldFloatingDividers = this.floatingDividers; this.floatingDividers = floatingDividers; firePCS("floatingDividers", new Boolean( oldFloatingDividers ), new Boolean( floatingDividers )); } /** * @return the value of the removeDividers property * @see #setRemoveDividers */ public boolean getRemoveDividers() { return removeDividers; } /** * If true, the next divider is removed when a component is removed from the * layout. If false, only the node itself is removed. Normally the next * divider should be removed from the layout when a component is removed. * @param removeDividers true to removed the next divider whena component is * removed from teh layout */ public void setRemoveDividers( boolean removeDividers ) { boolean oldRemoveDividers = this.removeDividers; this.removeDividers = removeDividers; firePCS("removeDividers", new Boolean( oldRemoveDividers ), new Boolean( removeDividers )); } /** * Add a component to this MultiSplitLayout. The * name should match the name property of the Leaf * node that represents the bounds of child. After * layoutContainer() recomputes the bounds of all of the nodes in * the model, it will set this child's bounds to the bounds of the * Leaf node with name. Note: if a component was already * added with the same name, this method does not remove it from * its parent. * * @param name identifies the Leaf node that defines the child's bounds * @param child the component to be added * @see #removeLayoutComponent */ public void addLayoutComponent(String name, Component child) { if (name == null) { throw new IllegalArgumentException("name not specified"); } childMap.put(name, child); } /** * Removes the specified component from the layout. * * @param child the component to be removed * @see #addLayoutComponent */ public void removeLayoutComponent(Component child) { String name = getNameForComponent( child ); if ( name != null ) { childMap.remove( name ); } } /** * Removes the specified node from the layout. * * @param name the name of the component to be removed * @see #addLayoutComponent */ public void removeLayoutNode(String name) { if ( name != null ) { Node n; if ( !( model instanceof Split )) n = model; else n = getNodeForName( name ); childMap.remove(name); if ( n != null ) { Split s = n.getParent(); s.remove( n ); if (removeDividers) { while ( s.getChildren().size() < 2 ) { Split p = s.getParent(); if ( p == null ) { if ( s.getChildren().size() > 0 ) model = (Node)s.getChildren().get( 0 ); else model = null; return; } if ( s.getChildren().size() == 1 ) { Node next = s.getChildren().get( 0 ); p.replace( s, next ); next.setParent( p ); } else p.remove( s ); s = p; } } } else { childMap.remove( name ); } } } /** * Show/Hide nodes. Any dividers that are no longer required due to one of the * nodes being made visible/invisible are also shown/hidder. The visibility of * the component managed by the node is also changed by this method * @param name the node name * @param visible the new node visible state */ public void displayNode( String name, boolean visible ) { Node node = getNodeForName( name ); if ( node != null ) { Component comp = getComponentForNode( node ); comp.setVisible( visible ); node.setVisible( visible ); MultiSplitLayout.Split p = node.getParent(); if ( !visible ) { p.hide( node ); if ( !p.isVisible()) p.getParent().hide( p ); p.checkDividers( p ); // If the split has become invisible then the parent may also have a // divider that needs to be hidden. while ( !p.isVisible()) { p = p.getParent(); if ( p != null ) p.checkDividers( p ); else break; } } else p.restoreDividers( p ); } setFloatingDividers( false ); } private Component childForNode(Node node) { if (node instanceof Leaf) { Leaf leaf = (Leaf)node; String name = leaf.getName(); return (name != null) ? childMap.get(name) : null; } return null; } private Dimension preferredComponentSize(Node node) { if ( layoutMode == NO_MIN_SIZE_LAYOUT ) return new Dimension(0, 0); Component child = childForNode(node); return ((child != null) && child.isVisible() ) ? child.getPreferredSize() : new Dimension(0, 0); } private Dimension minimumComponentSize(Node node) { if ( layoutMode == NO_MIN_SIZE_LAYOUT ) return new Dimension(0, 0); Component child = childForNode(node); return ((child != null) && child.isVisible() ) ? child.getMinimumSize() : new Dimension(0, 0); } private Dimension preferredNodeSize(Node root) { if (root instanceof Leaf) { return preferredComponentSize(root); } else if (root instanceof Divider) { if ( !((Divider)root).isVisible()) return new Dimension(0,0); int divSize = getDividerSize(); return new Dimension(divSize, divSize); } else { Split split = (Split)root; List splitChildren = split.getChildren(); int width = 0; int height = 0; if (split.isRowLayout()) { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = preferredNodeSize(splitChild); width += size.width; height = Math.max(height, size.height); } } else { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = preferredNodeSize(splitChild); width = Math.max(width, size.width); height += size.height; } } return new Dimension(width, height); } } /** * Get the minimum size of this node. Sums the minumum sizes of rows or * columns to get the overall minimum size for the layout node, including the * dividers. * @param root the node whose size is required. * @return the minimum size. */ public Dimension minimumNodeSize(Node root) { assert( root.isVisible ); if (root instanceof Leaf) { if ( layoutMode == NO_MIN_SIZE_LAYOUT ) return new Dimension(0, 0); Component child = childForNode(root); return ((child != null) && child.isVisible() ) ? child.getMinimumSize() : new Dimension(0, 0); } else if (root instanceof Divider) { if ( !((Divider)root).isVisible() ) return new Dimension(0,0); int divSize = getDividerSize(); return new Dimension(divSize, divSize); } else { Split split = (Split)root; List splitChildren = split.getChildren(); int width = 0; int height = 0; if (split.isRowLayout()) { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = minimumNodeSize(splitChild); width += size.width; height = Math.max(height, size.height); } } else { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = minimumNodeSize(splitChild); width = Math.max(width, size.width); height += size.height; } } return new Dimension(width, height); } } /** * Get the maximum size of this node. Sums the minumum sizes of rows or * columns to get the overall maximum size for the layout node, including the * dividers. * @param root the node whose size is required. * @return the minimum size. */ public Dimension maximumNodeSize(Node root) { assert( root.isVisible ); if (root instanceof Leaf) { Component child = childForNode(root); return ((child != null) && child.isVisible() ) ? child.getMaximumSize() : new Dimension(0, 0); } else if (root instanceof Divider) { if ( !((Divider)root).isVisible() ) return new Dimension(0,0); int divSize = getDividerSize(); return new Dimension(divSize, divSize); } else { Split split = (Split)root; List splitChildren = split.getChildren(); int width = Integer.MAX_VALUE; int height = Integer.MAX_VALUE; if (split.isRowLayout()) { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = maximumNodeSize(splitChild); width += size.width; height = Math.min(height, size.height); } } else { for(Node splitChild : splitChildren) { if ( !splitChild.isVisible()) continue; Dimension size = maximumNodeSize(splitChild); width = Math.min(width, size.width); height += size.height; } } return new Dimension(width, height); } } private Dimension sizeWithInsets(Container parent, Dimension size) { Insets insets = parent.getInsets(); int width = size.width + insets.left + insets.right; int height = size.height + insets.top + insets.bottom; return new Dimension(width, height); } public Dimension preferredLayoutSize(Container parent) { Dimension size = preferredNodeSize(getModel()); return sizeWithInsets(parent, size); } public Dimension minimumLayoutSize(Container parent) { Dimension size = minimumNodeSize(getModel()); return sizeWithInsets(parent, size); } private Rectangle boundsWithYandHeight(Rectangle bounds, double y, double height) { Rectangle r = new Rectangle(); r.setBounds((int)(bounds.getX()), (int)y, (int)(bounds.getWidth()), (int)height); return r; } private Rectangle boundsWithXandWidth(Rectangle bounds, double x, double width) { Rectangle r = new Rectangle(); r.setBounds((int)x, (int)(bounds.getY()), (int)width, (int)(bounds.getHeight())); return r; } private void minimizeSplitBounds(Split split, Rectangle bounds) { assert ( split.isVisible()); Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0); List splitChildren = split.getChildren(); Node lastChild = null; int lastVisibleChildIdx = splitChildren.size(); do { lastVisibleChildIdx--; lastChild = splitChildren.get( lastVisibleChildIdx ); } while (( lastVisibleChildIdx > 0 ) && !lastChild.isVisible()); if ( !lastChild.isVisible()) return; if ( lastVisibleChildIdx >= 0 ) { Rectangle lastChildBounds = lastChild.getBounds(); if (split.isRowLayout()) { int lastChildMaxX = lastChildBounds.x + lastChildBounds.width; splitBounds.add(lastChildMaxX, bounds.y + bounds.height); } else { int lastChildMaxY = lastChildBounds.y + lastChildBounds.height; splitBounds.add(bounds.x + bounds.width, lastChildMaxY); } } split.setBounds(splitBounds); } private void layoutShrink(Split split, Rectangle bounds) { Rectangle splitBounds = split.getBounds(); ListIterator splitChildren = split.getChildren().listIterator(); Node lastWeightedChild = split.lastWeightedChild(); if (split.isRowLayout()) { int totalWidth = 0; // sum of the children's widths int minWeightedWidth = 0; // sum of the weighted childrens' min widths int totalWeightedWidth = 0; // sum of the weighted childrens' widths for(Node splitChild : split.getChildren()) { if ( !splitChild.isVisible()) continue; int nodeWidth = splitChild.getBounds().width; int nodeMinWidth = 0; if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider )) nodeMinWidth = userMinSize; else if ( layoutMode == DEFAULT_LAYOUT ) nodeMinWidth = Math.min(nodeWidth, minimumNodeSize(splitChild).width); totalWidth += nodeWidth; if (splitChild.getWeight() > 0.0) { minWeightedWidth += nodeMinWidth; totalWeightedWidth += nodeWidth; } } double x = bounds.getX(); double extraWidth = splitBounds.getWidth() - bounds.getWidth(); double availableWidth = extraWidth; boolean onlyShrinkWeightedComponents = (totalWeightedWidth - minWeightedWidth) > extraWidth; while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { if ( splitChildren.hasNext()) splitChildren.next(); continue; } Rectangle splitChildBounds = splitChild.getBounds(); double minSplitChildWidth = 0.0; if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider )) minSplitChildWidth = userMinSize; else if ( layoutMode == DEFAULT_LAYOUT ) minSplitChildWidth = minimumNodeSize(splitChild).getWidth(); double splitChildWeight = (onlyShrinkWeightedComponents) ? splitChild.getWeight() : (splitChildBounds.getWidth() / (double)totalWidth); if (!splitChildren.hasNext()) { double newWidth = Math.max(minSplitChildWidth, bounds.getMaxX() - x); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); } if ( splitChild.isVisible()) { if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) { double oldWidth = splitChildBounds.getWidth(); double newWidth; if ( splitChild instanceof Divider ) { newWidth = dividerSize; } else { double allocatedWidth = Math.rint(splitChildWeight * extraWidth); newWidth = Math.max(minSplitChildWidth, oldWidth - allocatedWidth); } Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); availableWidth -= (oldWidth - splitChild.getBounds().getWidth()); } else { double existingWidth = splitChildBounds.getWidth(); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth); layout2(splitChild, newSplitChildBounds); } x = splitChild.getBounds().getMaxX(); } } } else { int totalHeight = 0; // sum of the children's heights int minWeightedHeight = 0; // sum of the weighted childrens' min heights int totalWeightedHeight = 0; // sum of the weighted childrens' heights for(Node splitChild : split.getChildren()) { if ( !splitChild.isVisible()) continue; int nodeHeight = splitChild.getBounds().height; int nodeMinHeight = 0; if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider )) nodeMinHeight = userMinSize; else if ( layoutMode == DEFAULT_LAYOUT ) nodeMinHeight = Math.min(nodeHeight, minimumNodeSize(splitChild).height); totalHeight += nodeHeight; if (splitChild.getWeight() > 0.0) { minWeightedHeight += nodeMinHeight; totalWeightedHeight += nodeHeight; } } double y = bounds.getY(); double extraHeight = splitBounds.getHeight() - bounds.getHeight(); double availableHeight = extraHeight; boolean onlyShrinkWeightedComponents = (totalWeightedHeight - minWeightedHeight) > extraHeight; while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { if ( splitChildren.hasNext()) splitChildren.next(); continue; } Rectangle splitChildBounds = splitChild.getBounds(); double minSplitChildHeight = 0.0; if (( layoutMode == USER_MIN_SIZE_LAYOUT ) && !( splitChild instanceof Divider )) minSplitChildHeight = userMinSize; else if ( layoutMode == DEFAULT_LAYOUT ) minSplitChildHeight = minimumNodeSize(splitChild).getHeight(); double splitChildWeight = (onlyShrinkWeightedComponents) ? splitChild.getWeight() : (splitChildBounds.getHeight() / (double)totalHeight); // If this split child is the last visible node it should all the // remaining space if ( !hasMoreVisibleSiblings( splitChild )) { double oldHeight = splitChildBounds.getHeight(); double newHeight; if ( splitChild instanceof Divider ) { newHeight = dividerSize; } else { newHeight = Math.max(minSplitChildHeight, bounds.getMaxY() - y); } Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= (oldHeight - splitChild.getBounds().getHeight()); } else /*if ( splitChild.isVisible()) {*/ if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) { double newHeight; double oldHeight = splitChildBounds.getHeight(); // Prevent the divider from shrinking if ( splitChild instanceof Divider ) { newHeight = dividerSize; } else { double allocatedHeight = Math.rint(splitChildWeight * extraHeight); newHeight = Math.max(minSplitChildHeight, oldHeight - allocatedHeight); } Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= (oldHeight - splitChild.getBounds().getHeight()); } else { double existingHeight = splitChildBounds.getHeight(); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight); layout2(splitChild, newSplitChildBounds); } y = splitChild.getBounds().getMaxY(); } } /* The bounds of the Split node root are set to be * big enough to contain all of its children. Since * Leaf children can't be reduced below their * (corresponding java.awt.Component) minimum sizes, * the size of the Split's bounds maybe be larger than * the bounds we were asked to fit within. */ minimizeSplitBounds(split, bounds); } /** * Check if the specified node has any following visible siblings * @param splitChild the node to check * @param true if there are visible children following */ private boolean hasMoreVisibleSiblings( Node splitChild ) { Node next = splitChild.nextSibling(); if ( next == null ) return false; do { if ( next.isVisible()) return true; next = next.nextSibling(); } while ( next != null ); return false; } private void layoutGrow(Split split, Rectangle bounds) { Rectangle splitBounds = split.getBounds(); ListIterator splitChildren = split.getChildren().listIterator(); Node lastWeightedChild = split.lastWeightedChild(); /* Layout the Split's child Nodes' along the X axis. The bounds * of each child will have the same y coordinate and height as the * layoutGrow() bounds argument. Extra width is allocated to the * to each child with a non-zero weight: * newWidth = currentWidth + (extraWidth * splitChild.getWeight()) * Any extraWidth "left over" (that's availableWidth in the loop * below) is given to the last child. Note that Dividers always * have a weight of zero, and they're never the last child. */ if (split.isRowLayout()) { double x = bounds.getX(); double extraWidth = bounds.getWidth() - splitBounds.getWidth(); double availableWidth = extraWidth; while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { continue; } Rectangle splitChildBounds = splitChild.getBounds(); double splitChildWeight = splitChild.getWeight(); if ( !hasMoreVisibleSiblings( splitChild )) { double newWidth = bounds.getMaxX() - x; Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); } else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) { double allocatedWidth = (splitChild.equals(lastWeightedChild)) ? availableWidth : Math.rint(splitChildWeight * extraWidth); double newWidth = splitChildBounds.getWidth() + allocatedWidth; Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); availableWidth -= allocatedWidth; } else { double existingWidth = splitChildBounds.getWidth(); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth); layout2(splitChild, newSplitChildBounds); } x = splitChild.getBounds().getMaxX(); } } /* Layout the Split's child Nodes' along the Y axis. The bounds * of each child will have the same x coordinate and width as the * layoutGrow() bounds argument. Extra height is allocated to the * to each child with a non-zero weight: * newHeight = currentHeight + (extraHeight * splitChild.getWeight()) * Any extraHeight "left over" (that's availableHeight in the loop * below) is given to the last child. Note that Dividers always * have a weight of zero, and they're never the last child. */ else { double y = bounds.getY(); double extraHeight = bounds.getHeight() - splitBounds.getHeight(); double availableHeight = extraHeight; while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { continue; } Rectangle splitChildBounds = splitChild.getBounds(); double splitChildWeight = splitChild.getWeight(); if (!splitChildren.hasNext()) { double newHeight = bounds.getMaxY() - y; Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); } else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) { double allocatedHeight = (splitChild.equals(lastWeightedChild)) ? availableHeight : Math.rint(splitChildWeight * extraHeight); double newHeight = splitChildBounds.getHeight() + allocatedHeight; Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= allocatedHeight; } else { double existingHeight = splitChildBounds.getHeight(); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight); layout2(splitChild, newSplitChildBounds); } y = splitChild.getBounds().getMaxY(); } } } /* Second pass of the layout algorithm: branch to layoutGrow/Shrink * as needed. */ private void layout2(Node root, Rectangle bounds) { if (root instanceof Leaf) { Component child = childForNode(root); if (child != null) { child.setBounds(bounds); } root.setBounds(bounds); } else if (root instanceof Divider) { root.setBounds(bounds); } else if (root instanceof Split) { Split split = (Split)root; boolean grow = split.isRowLayout() ? (split.getBounds().width <= bounds.width) : (split.getBounds().height <= bounds.height); if (grow) { layoutGrow(split, bounds); root.setBounds(bounds); } else { layoutShrink(split, bounds); // split.setBounds() called in layoutShrink() } } } /* First pass of the layout algorithm. * * If the Dividers are "floating" then set the bounds of each * node to accomodate the preferred size of all of the * Leaf's java.awt.Components. Otherwise, just set the bounds * of each Leaf/Split node so that it's to the left of (for * Split.isRowLayout() Split children) or directly above * the Divider that follows. * * This pass sets the bounds of each Node in the layout model. It * does not resize any of the parent Container's * (java.awt.Component) children. That's done in the second pass, * see layoutGrow() and layoutShrink(). */ private void layout1(Node root, Rectangle bounds) { if (root instanceof Leaf) { root.setBounds(bounds); } else if (root instanceof Split) { Split split = (Split)root; Iterator splitChildren = split.getChildren().iterator(); Rectangle childBounds = null; int divSize = getDividerSize(); boolean initSplit = false; /* Layout the Split's child Nodes' along the X axis. The bounds * of each child will have the same y coordinate and height as the * layout1() bounds argument. * * Note: the column layout code - that's the "else" clause below * this if, is identical to the X axis (rowLayout) code below. */ if (split.isRowLayout()) { double x = bounds.getX(); while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { if ( splitChildren.hasNext()) splitChildren.next(); continue; } Divider dividerChild = (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null; double childWidth = 0.0; if (getFloatingDividers()) { childWidth = preferredNodeSize(splitChild).getWidth(); } else { if ((dividerChild != null) && dividerChild.isVisible()) { double cw = dividerChild.getBounds().getX() - x; if ( cw > 0.0 ) childWidth = cw; else { childWidth = preferredNodeSize(splitChild).getWidth(); initSplit = true; } } else { childWidth = split.getBounds().getMaxX() - x; } } childBounds = boundsWithXandWidth(bounds, x, childWidth); layout1(splitChild, childBounds); if (( initSplit || getFloatingDividers()) && (dividerChild != null) && dividerChild.isVisible()) { double dividerX = childBounds.getMaxX(); Rectangle dividerBounds; dividerBounds = boundsWithXandWidth(bounds, dividerX, divSize); dividerChild.setBounds(dividerBounds); } if ((dividerChild != null) && dividerChild.isVisible()) { x = dividerChild.getBounds().getMaxX(); } } } /* Layout the Split's child Nodes' along the Y axis. The bounds * of each child will have the same x coordinate and width as the * layout1() bounds argument. The algorithm is identical to what's * explained above, for the X axis case. */ else { double y = bounds.getY(); while(splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if ( !splitChild.isVisible()) { if ( splitChildren.hasNext()) splitChildren.next(); continue; } Divider dividerChild = (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null; double childHeight = 0.0; if (getFloatingDividers()) { childHeight = preferredNodeSize(splitChild).getHeight(); } else { if ((dividerChild != null) && dividerChild.isVisible()) { double cy = dividerChild.getBounds().getY() - y; if ( cy > 0.0 ) childHeight = cy; else { childHeight = preferredNodeSize(splitChild).getHeight(); initSplit = true; } } else { childHeight = split.getBounds().getMaxY() - y; } } childBounds = boundsWithYandHeight(bounds, y, childHeight); layout1(splitChild, childBounds); if (( initSplit || getFloatingDividers()) && (dividerChild != null) && dividerChild.isVisible()) { double dividerY = childBounds.getMaxY(); Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, divSize); dividerChild.setBounds(dividerBounds); } if ((dividerChild != null) && dividerChild.isVisible()) { y = dividerChild.getBounds().getMaxY(); } } } /* The bounds of the Split node root are set to be just * big enough to contain all of its children, but only * along the axis it's allocating space on. That's * X for rows, Y for columns. The second pass of the * layout algorithm - see layoutShrink()/layoutGrow() * allocates extra space. */ minimizeSplitBounds(split, bounds); } } /** * Get the layout mode * @return current layout mode */ public int getLayoutMode() { return layoutMode; } /** * Set the layout mode. By default this layout uses the preferred and minimum * sizes of the child components. To ignore the minimum size set the layout * mode to MultiSplitLayout.LAYOUT_NO_MIN_SIZE. * @param layoutMode the layout mode *