swingx-master/swingx-core/src/main/java/org/jdesktop/swingx/MultiSplitLayout.java
/*
 * $Id$
 *
 * 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.Serializable;
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.
 *
 * <p>
 * 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.
 *
 * <p>
 * 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, Serializable
{
  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<String, Component> childMap = new HashMap<String, Component>();
  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<Node> 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 if (model instanceof Leaf) {
      if (((Leaf) model).getName().equals(name)) {
        return model;
      } else {
        return null;
      }
    } 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<String,Component> 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
   * <code>name</code> should match the name property of the Leaf
   * node that represents the bounds of <code>child</code>.  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 <code>name</code>.  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
   */
  @Override
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
   */
  @Override
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 = 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/hidden. 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<Node> 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<Node> 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<Node> 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);
  }
  
  @Override
public Dimension preferredLayoutSize(Container parent) {
    Dimension size = preferredNodeSize(getModel());
    return sizeWithInsets(parent, size);
  }
  
  @Override
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<Node> 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<Node> 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() / 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() / 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<Node> 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<Node> 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
   * <ul>
   * <li>DEFAULT_LAYOUT - use the preferred and minimum sizes when sizing the children</li>
   * <li>LAYOUT_NO_MIN_SIZE - ignore the minimum size when sizing the children</li>
   * </li>
   */
  public void setLayoutMode( int layoutMode )
  {
    this.layoutMode = layoutMode;
  }

  /**
   * Get the minimum node size 
   * @return the minimum size
   */
  public int getUserMinSize()
  {
    return userMinSize;
  }

  /**
   * Set the user defined minimum size support in the USER_MIN_SIZE_LAYOUT 
   * layout mode.
   * @param minSize the new minimum size
   */
  public void setUserMinSize( int minSize )
  {
    userMinSize = minSize;
  }

  /**
   * Get the layoutByWeight falg. If the flag is true the layout initializes
   * itself using the model weights
   * @return the layoutByWeight
   */
  public boolean getLayoutByWeight()
  {
    return layoutByWeight;
  }

  /**
   * Sset the layoutByWeight falg. If the flag is true the layout initializes
   * itself using the model weights
   * @param state the new layoutByWeight to set
   */
  public void setLayoutByWeight( boolean state )
  {
    layoutByWeight = state;
  }
  
  /**
   * The specified Node is either the wrong type or was configured
   * incorrectly.
   */
  public static class InvalidLayoutException extends RuntimeException {
    private final Node node;
    public InvalidLayoutException(String msg, Node node) {
      super(msg);
      this.node = node;
    }
    /**
     * @return the invalid Node.
     */
    public Node getNode() { return node; }
  }
  
  private void throwInvalidLayout(String msg, Node node) {
    throw new InvalidLayoutException(msg, node);
  }
  
  private void checkLayout(Node root) {
    if (root instanceof Split) {
      Split split = (Split)root;
      if (split.getChildren().size() <= 2) {
        throwInvalidLayout("Split must have > 2 children", root);
      }
      Iterator<Node> splitChildren = split.getChildren().iterator();
      double weight = 0.0;
      while(splitChildren.hasNext())       {
        Node splitChild = splitChildren.next();
        if ( !splitChild.isVisible()) { 
          if ( splitChildren.hasNext()) 
            splitChildren.next();
          continue;
        }
        if (splitChild instanceof Divider) {
          continue;
          //throwInvalidLayout("expected a Split or Leaf Node", splitChild);
        }
        if (splitChildren.hasNext()) {
          Node dividerChild = splitChildren.next();
          if (!(dividerChild instanceof Divider)) {
            throwInvalidLayout("expected a Divider Node", dividerChild);
          }
        }
        weight += splitChild.getWeight();
        checkLayout(splitChild);
      }
      if (weight > 1.0) {
        throwInvalidLayout("Split children's total weight > 1.0", root);
      }
    }
  }
  
  /**
   * Compute the bounds of all of the Split/Divider/Leaf Nodes in
   * the layout model, and then set the bounds of each child component
   * with a matching Leaf Node.
   */
  @Override
public void layoutContainer(Container parent)
  {
    if ( layoutByWeight && floatingDividers )
      doLayoutByWeight( parent );

    checkLayout(getModel());
    Insets insets = parent.getInsets();
    Dimension size = parent.getSize();
    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);
    layout1(getModel(), bounds);
    layout2(getModel(), bounds);
  }
  
  
  private Divider dividerAt(Node root, int x, int y) {
    if (root instanceof Divider) {
      Divider divider = (Divider)root;
      return (divider.getBounds().contains(x, y)) ? divider : null;
    }
    else if (root instanceof Split) {
      Split split = (Split)root;
        for(Node child : split.getChildren()) {
        if ( !child.isVisible())
          continue;
        if (child.getBounds().contains(x, y)) {
          return dividerAt(child, x, y);
        }
      }
    }
    return null;
  }
  
  /**
   * Return the Divider whose bounds contain the specified
   * point, or null if there isn't one.
   *
   * @param x x coordinate
   * @param y y coordinate
   * @return the Divider at x,y
   */
  public Divider dividerAt(int x, int y) {
    return dividerAt(getModel(), x, y);
  }
  
  private boolean nodeOverlapsRectangle(Node node, Rectangle r2) {
    Rectangle r1 = node.getBounds();
    return
      (r1.x <= (r2.x + r2.width)) && ((r1.x + r1.width) >= r2.x) &&
      (r1.y <= (r2.y + r2.height)) && ((r1.y + r1.height) >= r2.y);
  }
  
  private List<Divider> dividersThatOverlap(Node root, Rectangle r) {
    if (nodeOverlapsRectangle(root, r) && (root instanceof Split)) {
        List<Divider> dividers = new ArrayList<Divider>();
        for(Node child : ((Split)root).getChildren()) {
        if (child instanceof Divider) {
          if (nodeOverlapsRectangle(child, r)) {
            dividers.add((Divider)child);
          }
        }
        else if (child instanceof Split) {
          dividers.addAll(dividersThatOverlap(child, r));
        }
      }
      return dividers;
    }
    else {
      return Collections.emptyList();
    }
  }
  
  /**
   * Return the Dividers whose bounds overlap the specified
   * Rectangle.
   *
   * @param r target Rectangle
   * @return the Dividers that overlap r
   * @throws IllegalArgumentException if the Rectangle is null
   */
  public List<Divider> dividersThatOverlap(Rectangle r) {
    if (r == null) {
      throw new IllegalArgumentException("null Rectangle");
    }
    return dividersThatOverlap(getModel(), r);
  }
  
  
  /**
   * Base class for the nodes that model a MultiSplitLayout.
   */
  public static abstract class Node implements Serializable {
    private Split parent = null;
    private Rectangle bounds = new Rectangle();
    private double weight = 0.0;
    private boolean isVisible = true;
    public void setVisible( boolean b ) {
      isVisible = b;
    }
        
    /**
     * Determines whether this node should be visible when its
     * parent is visible. Nodes are 
     * initially visible
     * @return <code>true</code> if the node is visible,
     * <code>false</code> otherwise
     */
    public boolean isVisible() {
      return isVisible;
    }
    
    /**
     * Returns the Split parent of this Node, or null.
     *
     * @return the value of the parent property.
     * @see #setParent
     */
    public Split getParent() { return parent; }
    
    /**
     * Set the value of this Node's parent property.  The default
     * value of this property is null.
     *
     * @param parent a Split or null
     * @see #getParent
     */
    public void setParent(Split parent) {
      this.parent = parent;
    }
    
    /**
     * Returns the bounding Rectangle for this Node.
     *
     * @return the value of the bounds property.
     * @see #setBounds
     */
    public Rectangle getBounds() {
      return new Rectangle(this.bounds);
    }
    
    /**
     * Set the bounding Rectangle for this node.  The value of
     * bounds may not be null.  The default value of bounds
     * is equal to <code>new Rectangle(0,0,0,0)</code>.
     *
     * @param bounds the new value of the bounds property
     * @throws IllegalArgumentException if bounds is null
     * @see #getBounds
     */
    public void setBounds(Rectangle bounds) {
      if (bounds == null) {
        throw new IllegalArgumentException("null bounds");
      }
     this.bounds = new Rectangle(bounds);
    }
    
    /**
     * Value between 0.0 and 1.0 used to compute how much space
     * to add to this sibling when the layout grows or how
     * much to reduce when the layout shrinks.
     *
     * @return the value of the weight property
     * @see #setWeight
     */
    public double getWeight() { return weight; }
    
    /**
     * The weight property is a between 0.0 and 1.0 used to
     * compute how much space to add to this sibling when the
     * layout grows or how much to reduce when the layout shrinks.
     * If rowLayout is true then this node's width grows
     * or shrinks by (extraSpace * weight).  If rowLayout is false,
     * then the node's height is changed.  The default value
     * of weight is 0.0.
     *
     * @param weight a double between 0.0 and 1.0
     * @see #getWeight
     * @see MultiSplitLayout#layoutContainer
     * @throws IllegalArgumentException if weight is not between 0.0 and 1.0
     */
    public void setWeight(double weight) {
      if ((weight < 0.0)|| (weight > 1.0)) {
        throw new IllegalArgumentException("invalid weight");
      }
      this.weight = weight;
    }
    
    private Node siblingAtOffset(int offset) {
      Split p = getParent();
      if (p == null) { return null; }
      List<Node> siblings = p.getChildren();
      int index = siblings.indexOf(this);
      if (index == -1) { return null; }
      index += offset;
      return ((index > -1) && (index < siblings.size())) ? siblings.get(index) : null;
    }
    
    /**
     * Return the Node that comes after this one in the parent's
     * list of children, or null.  If this node's parent is null,
     * or if it's the last child, then return null.
     *
     * @return the Node that comes after this one in the parent's list of children.
     * @see #previousSibling
     * @see #getParent
     */
    public Node nextSibling() {
      return siblingAtOffset(+1);
    }
    
    /**
     * Return the Node that comes before this one in the parent's
     * list of children, or null.  If this node's parent is null,
     * or if it's the last child, then return null.
     *
     * @return the Node that comes before this one in the parent's list of children.
     * @see #nextSibling
     * @see #getParent
     */
    public Node previousSibling() {
      return siblingAtOffset(-1);
    }
  }

  public static class RowSplit extends Split {
    public RowSplit() {
    }

    public RowSplit(Node... children) {
        setChildren(children);
    }
    
    /**
     * Returns true if the this Split's children are to be 
     * laid out in a row: all the same height, left edge
     * equal to the previous Node's right edge.  If false,
     * children are laid on in a column.
     * 
     * @return the value of the rowLayout property.
     * @see #setRowLayout
     */
    @Override
    public final boolean isRowLayout() { return true; }
  }
    
  public static class ColSplit extends Split {
    public ColSplit() {
    }

    public ColSplit(Node... children) {
        setChildren(children);
    }
    
    /**
     * Returns true if the this Split's children are to be 
     * laid out in a row: all the same height, left edge
     * equal to the previous Node's right edge.  If false,
     * children are laid on in a column.
     * 
     * @return the value of the rowLayout property.
     * @see #setRowLayout
     */
    @Override
    public final boolean isRowLayout() { return false; }
  }

  /**
   * Defines a vertical or horizontal subdivision into two or more
   * tiles.
   */
  public static class Split extends Node {
    private List<Node> children = Collections.emptyList();
    private boolean rowLayout = true;
    private String name;

    public Split(Node... children) {
      setChildren(children);
    }
    
    /**
     * Default constructor to support xml (de)serialization and other bean spec dependent ops.
     * Resulting instance of Split is invalid until setChildren() is called.
     */
    public Split() {
    }

    /**
     * Determines whether this node should be visible when its
     * parent is visible. Nodes are 
     * initially visible
     * @return <code>true</code> if the node is visible,
     * <code>false</code> otherwise
     */
    @Override
    public boolean isVisible() {
        for(Node child : children) {
        if ( child.isVisible() && !( child instanceof Divider ))
          return true;
      }
      return false;
    }
    
    /**
     * Returns true if the this Split's children are to be
     * laid out in a row: all the same height, left edge
     * equal to the previous Node's right edge.  If false,
     * children are laid on in a column.
     *
     * @return the value of the rowLayout property.
     * @see #setRowLayout
     */
    public boolean isRowLayout() { return rowLayout; }
    
    /**
     * Set the rowLayout property.  If true, all of this Split's
     * children are to be laid out in a row: all the same height,
     * each node's left edge equal to the previous Node's right
     * edge.  If false, children are laid on in a column.  Default
     * value is true.
     *
     * @param rowLayout true for horizontal row layout, false for column
     * @see #isRowLayout
     */
    public void setRowLayout(boolean rowLayout) {
      this.rowLayout = rowLayout;
    }
    
    /**
     * Returns this Split node's children.  The returned value
     * is not a reference to the Split's internal list of children
     *
     * @return the value of the children property.
     * @see #setChildren
     */
    public List<Node> getChildren() {
      return new ArrayList<Node>(children);
    }
    
    
    /**
     * Remove a node from the layout. Any sibling dividers will also be removed
     * @param n the node to be removed
     */
    public void remove( Node n ) { 
      if ( n.nextSibling() instanceof Divider )
        children.remove( n.nextSibling() );
      else if ( n.previousSibling() instanceof Divider )
        children.remove( n.previousSibling() );
      children.remove( n );
    }
    
    /**
     * Replace one node with another. This method is used when a child is removed 
     * from a split and the split is no longer required, in which case the 
     * remaining node in the child split can replace the split in the parent 
     * node
     * @param target the node being replaced
     * @param replacement the replacement node
     */
    public void replace( Node target, Node replacement ) { 
      int idx = children.indexOf( target );
      children.remove( target );
      children.add( idx, replacement );

      replacement.setParent ( this );
      target.setParent( this );
    }
    
    /**
     * Change a node to being hidden. Any associated divider nodes are also hidden
     * @param target the node to hide
     */
    public void hide( Node target ){ 
      Node next = target.nextSibling();
      if ( next instanceof Divider )
        next.setVisible( false );
      else {
        Node prev = target.previousSibling();
        if ( prev instanceof Divider )
          prev.setVisible( false );
      }
      target.setVisible( false );
    }
    
    /**
     * Check the dividers to ensure that redundant dividers are hidden and do
     * not interfere in the layout, for example when all the children of a split 
     * are hidden (the split is then invisible), so two dividers may otherwise
     * appear next to one another.
     * @param split the split to check
     */
    public void checkDividers( Split split ) { 
      ListIterator<Node> splitChildren = split.getChildren().listIterator();
      while( splitChildren.hasNext()) {
        Node splitChild = splitChildren.next();
        if ( !splitChild.isVisible()) {
          continue;
        }
        else if ( splitChildren.hasNext()) {
          Node dividerChild = splitChildren.next();
          if ( dividerChild instanceof Divider ) {
            if ( splitChildren.hasNext()) {
              Node rightChild = splitChildren.next();
              while ( !rightChild.isVisible()) {
                rightChild = rightChild.nextSibling();
                if ( rightChild == null ) {
                  // No visible right sibling found, so hide the divider
                  dividerChild.setVisible( false );
                  break;
                }
              }

              // A visible child is found but it's a divider and therefore 
              // we have to visible and adjacent dividers - so we hide one
              if (( rightChild != null ) && ( rightChild instanceof Divider ))
                dividerChild.setVisible( false );
            }
          }
          else if (( splitChild instanceof Divider ) && ( dividerChild instanceof Divider )) {
            splitChild.setVisible( false );
          }
        }
      }
    }
    
    /**
     * Restore any of the hidden dividers that are required to separate visible nodes
     * @param split the node to check
     */
    public void restoreDividers( Split split ) { 
      boolean nextDividerVisible = false;
      ListIterator<Node> splitChildren = split.getChildren().listIterator();
      while( splitChildren.hasNext()) {
        Node splitChild = splitChildren.next();
        if ( splitChild instanceof Divider ) {
          Node prev = splitChild.previousSibling();
          if ( prev.isVisible()) { 
            Node next = splitChild.nextSibling();
            while ( next != null ) {
              if ( next.isVisible()) {
                splitChild.setVisible( true );
                break;
              }
              next = next.nextSibling();
            }
          }
        }
      }
      if ( split.getParent() != null )
        restoreDividers( split.getParent());
    }
    
    /**
     * Set's the children property of this Split node.  The parent
     * of each new child is set to this Split node, and the parent
     * of each old child (if any) is set to null.  This method
     * defensively copies the incoming List.  Default value is
     * an empty List.
     *
     * @param children List of children
     * @see #getChildren
     * @throws IllegalArgumentException if children is null
     */
    public void setChildren(List<Node> children) {
      if (children == null) {
        throw new IllegalArgumentException("children must be a non-null List");
      }
        for(Node child : this.children) {
        child.setParent(null);
      }

      this.children = new ArrayList<Node>(children);
        for(Node child : this.children) {
        child.setParent(this);
      }
    }
       
    /**
     * Convenience method for setting the children of this Split node.  The parent
     * of each new child is set to this Split node, and the parent
     * of each old child (if any) is set to null.  This method
     * defensively copies the incoming array.
     * 
     * @param children array of children
     * @see #getChildren
     * @throws IllegalArgumentException if children is null
     */
    public void setChildren(Node... children) {
        setChildren(children == null ? null : Arrays.asList(children));
    }
        
    /**
     * Convenience method that returns the last child whose weight
     * is > 0.0.
     *
     * @return the last child whose weight is > 0.0.
     * @see #getChildren
     * @see Node#getWeight
     */
    public final Node lastWeightedChild() {
      List<Node> kids = getChildren();
      Node weightedChild = null;
        for(Node child : kids) {
        if ( !child.isVisible())
          continue;
        if (child.getWeight() > 0.0) {
          weightedChild = child;
        }
      }
      return weightedChild;
    }
    
    /**
     * Return the Leaf's name.
     *
     * @return the value of the name property.
     * @see #setName
     */
    public String getName() { return name; }
    
    /**
     * Set the value of the name property.  Name may not be null.
     *
     * @param name value of the name property
     * @throws IllegalArgumentException if name is null
     */
    public void setName(String name) {
      if (name == null) {
        throw new IllegalArgumentException("name is null");
      }
      this.name = name;
    }
        
    @Override
    public String toString() {
      int nChildren = getChildren().size();
      StringBuffer sb = new StringBuffer("MultiSplitLayout.Split");
      sb.append(" \"");
      sb.append(getName());
      sb.append("\"");
      sb.append(isRowLayout() ? " ROW [" : " COLUMN [");
      sb.append(nChildren + ((nChildren == 1) ? " child" : " children"));
      sb.append("] ");
      sb.append(getBounds());
      return sb.toString();
    }
  }
  
  
  /**
   * Models a java.awt Component child.
   */
  public static class Leaf extends Node {
    private String name = "";
    
    /**
     * Create a Leaf node.  The default value of name is "".
     */
    public Leaf() { }
    
  
    /**
     * Create a Leaf node with the specified name.  Name can not
     * be null.
     *
     * @param name value of the Leaf's name property
     * @throws IllegalArgumentException if name is null
     */
    public Leaf(String name) {
      if (name == null) {
        throw new IllegalArgumentException("name is null");
      }
      this.name = name;
    }
    
    /**
     * Return the Leaf's name.
     *
     * @return the value of the name property.
     * @see #setName
     */
    public String getName() { return name; }
    
    /**
     * Set the value of the name property.  Name may not be null.
     *
     * @param name value of the name property
     * @throws IllegalArgumentException if name is null
     */
    public void setName(String name) {
      if (name == null) {
        throw new IllegalArgumentException("name is null");
      }
      this.name = name;
    }
    
    @Override
    public String toString() {
      StringBuffer sb = new StringBuffer("MultiSplitLayout.Leaf");
      sb.append(" \"");
      sb.append(getName());
      sb.append("\"");
      sb.append(" weight=");
      sb.append(getWeight());
      sb.append(" ");
      sb.append(getBounds());
      return sb.toString();
    }
  }
  
  
  /**
   * Models a single vertical/horiztonal divider.
   */
  public static class Divider extends Node {   
    /**
     * Convenience method, returns true if the Divider's parent
     * is a Split row (a Split with isRowLayout() true), false
     * otherwise. In other words if this Divider's major axis
     * is vertical, return true.
     *
     * @return true if this Divider is part of a Split row.
     */
    public final boolean isVertical() {
      Split parent = getParent();
      return (parent != null) ? parent.isRowLayout() : false;
    }
    
    /**
     * Dividers can't have a weight, they don't grow or shrink.
     * @throws UnsupportedOperationException
     */
    @Override
    public void setWeight(double weight) {
      throw new UnsupportedOperationException();
    }
    
    @Override
    public String toString() {
      return "MultiSplitLayout.Divider " + getBounds().toString();
    }
  }
  
  
  private static void throwParseException(StreamTokenizer st, String msg) throws Exception {
    throw new Exception("MultiSplitLayout.parseModel Error: " + msg);
  }
  
  private static void parseAttribute(String name, StreamTokenizer st, Node node) throws Exception {
    if ((st.nextToken() != '=')) {
      throwParseException(st, "expected '=' after " + name);
    }
    if (name.equalsIgnoreCase("WEIGHT")) {
      if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
        node.setWeight(st.nval);
      }
      else {
        throwParseException(st, "invalid weight");
      }
    }
    else if (name.equalsIgnoreCase("NAME")) {
      if (st.nextToken() == StreamTokenizer.TT_WORD) {
        if (node instanceof Leaf) {
          ((Leaf)node).setName(st.sval);
        }
        else if (node instanceof Split) {
          ((Split)node).setName(st.sval);
        }
        else {
          throwParseException(st, "can't specify name for " + node);
        }
      }
      else {
        throwParseException(st, "invalid name");
      }
    }
    else {
      throwParseException(st, "unrecognized attribute \"" + name + "\"");
    }
  }
  
  private static void addSplitChild(Split parent, Node child) {
    List<Node> children = new ArrayList<Node>(parent.getChildren());
    if (children.size() == 0) {
      children.add(child);
    }
    else {
      children.add(new Divider());
      children.add(child);
    }
    parent.setChildren(children);
  }
  
  private static void parseLeaf(StreamTokenizer st, Split parent) throws Exception {
    Leaf leaf = new Leaf();
    int token;
    while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
      if (token == ')') {
        break;
      }
      if (token == StreamTokenizer.TT_WORD) {
        parseAttribute(st.sval, st, leaf);
      }
      else {
        throwParseException(st, "Bad Leaf: " + leaf);
      }
    }
    addSplitChild(parent, leaf);
  }
  
  private static void parseSplit(StreamTokenizer st, Split parent) throws Exception {
    int token;
    while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
      if (token == ')') {
        break;
      }
      else if (token == StreamTokenizer.TT_WORD) {
        if (st.sval.equalsIgnoreCase("WEIGHT")) {
          parseAttribute(st.sval, st, parent);
        }
        else if (st.sval.equalsIgnoreCase("NAME")) {
          parseAttribute(st.sval, st, parent);
        }
        else {
          addSplitChild(parent, new Leaf(st.sval));
        }
      }
      else if (token == '(') {
        if ((token = st.nextToken()) != StreamTokenizer.TT_WORD) {
          throwParseException(st, "invalid node type");
        }
        String nodeType = st.sval.toUpperCase();
        if (nodeType.equals("LEAF")) {
          parseLeaf(st, parent);
        }
        else if (nodeType.equals("ROW") || nodeType.equals("COLUMN")) {
          Split split = new Split();
          split.setRowLayout(nodeType.equals("ROW"));
          addSplitChild(parent, split);
          parseSplit(st, split);
        }
        else {
          throwParseException(st, "unrecognized node type '" + nodeType + "'");
        }
      }
    }
  }
  
  private static Node parseModel(Reader r) {
    StreamTokenizer st = new StreamTokenizer(r);
    try {
      Split root = new Split();
      parseSplit(st, root);
      return root.getChildren().get(0);
    }
    catch (Exception e) {
      System.err.println(e);
    }
    finally {
      try { r.close(); } catch (IOException ignore) {}
    }
    return null;
  }
  
  /**
   * A convenience method that converts a string to a
   * MultiSplitLayout model (a tree of Nodes) using a
   * a simple syntax.  Nodes are represented by
   * parenthetical expressions whose first token
   * is one of ROW/COLUMN/LEAF.  ROW and COLUMN specify
   * horizontal and vertical Split nodes respectively,
   * LEAF specifies a Leaf node.  A Leaf's name and
   * weight can be specified with attributes,
   * name=<i>myLeafName</i> weight=<i>myLeafWeight</i>.
   * Similarly, a Split's weight can be specified with
   * weight=<i>mySplitWeight</i>.
   *
   * <p> For example, the following expression generates
   * a horizontal Split node with three children:
   * the Leafs named left and right, and a Divider in
   * between:
   * <pre>
   * (ROW (LEAF name=left) (LEAF name=right weight=1.0))
   * </pre>
   *
   * <p> Dividers should not be included in the string,
   * they're added automatcially as needed.  Because
   * Leaf nodes often only need to specify a name, one
   * can specify a Leaf by just providing the name.
   * The previous example can be written like this:
   * <pre>
   * (ROW left (LEAF name=right weight=1.0))
   * </pre>
   *
   * <p>Here's a more complex example.  One row with
   * three elements, the first and last of which are columns
   * with two leaves each:
   * <pre>
   * (ROW (COLUMN weight=0.5 left.top left.bottom)
   *      (LEAF name=middle)
   *      (COLUMN weight=0.5 right.top right.bottom))
   * </pre>
   *
   *
   * <p> This syntax is not intended for archiving or
   * configuration files .  It's just a convenience for
   * examples and tests.
   *
   * @return the Node root of a tree based on s.
   */
  public static Node parseModel(String s) {
    return parseModel(new StringReader(s));
  }
  
  
  private static void printModel(String indent, Node root) {
    if (root instanceof Split) {
      Split split = (Split)root;
      System.out.println(indent + split);
        for(Node child : split.getChildren()) {
        printModel(indent + "  ", child);
      }
    }
    else {
      System.out.println(indent + root);
    }
  }
  
  /**
   * Print the tree with enough detail for simple debugging.
   */
  public static void printModel(Node root) {
    printModel("", root);
  }
}
