content: /*
   * Copyright 2008-2013 Andre Pfeiler
   *
   * This file is part of FindBugs-IDEA.
   *
   * FindBugs-IDEA is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * FindBugs-IDEA 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 General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with FindBugs-IDEA.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package org.twodividedbyzero.idea.findbugs.gui.common;
  
  import javax.swing.UIManager;
  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.Collections;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Locale;
  import java.util.Map;
  
  
  /**
   * 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/>
   * <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/>
   * <p/>
   * All properties in this class are bound: when a properties value
   * is changed, all PropertyChangeListeners are fired.
   *
   * @author Hans Muller
   * @author Andre Pfeiler - some minor adaptations
   * @see MultiSplitPane
   */
  
  @SuppressWarnings({"HardCodedStringLiteral", "NestedAssignment"})
  public class MultiSplitLayout implements LayoutManager {
  
  	private final Map<String, Component> childMap = new HashMap<String, Component>();
  	private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
  	private transient Node model;
  	private int dividerSize;
  	private boolean floatingDividers = true;
  
  
  	/**
  	 * Create a MultiSplitLayout with a default model with a single
  	 * Leaf node named "default".
  	 * <p/>
  	 * #see setModel
  	 */
  	public MultiSplitLayout() {
  		this(new Leaf("default"));
  	}
  
  
  	/**
  	 * Create a MultiSplitLayout with the specified model.
  	 * <p/>
  	 * #see setModel
  	 * @param model
  	 */
  	private MultiSplitLayout(final Node model) {
  		this.model = model;
  		dividerSize = UIManager.getInt("SplitPane.dividerSize");
  		if (dividerSize == 0) {
  			dividerSize = 7;
  		}
  	}
  
  
  	public void addPropertyChangeListener(final PropertyChangeListener listener) {
  		if (listener != null) {
  			pcs.addPropertyChangeListener(listener);
  		}
  	}
  
  
  	public void removePropertyChangeListener(final PropertyChangeListener listener) {
  		if (listener != null) {
  			pcs.removePropertyChangeListener(listener);
  		}
  	}
  
  
  	public PropertyChangeListener[] getPropertyChangeListeners() {
  		return pcs.getPropertyChangeListeners();
  	}
  
  
  	private void firePCS(final String propertyName, final Object oldValue, final 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
  	 */
  	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(final Node model) {
  		if (model == null || model instanceof Divider) {
  			throw new IllegalArgumentException("invalid model");
  		}
  		this.model = model;
  		firePCS("model", model, model);
  	}
  
  
  	/**
  	 * 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
  	 */
  	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(final int dividerSize) {
  		if (dividerSize < 0) {
  			throw new IllegalArgumentException("invalid dividerSize");
  		}
  		final int oldDividerSize = this.dividerSize;
  		this.dividerSize = dividerSize;
  		firePCS("dividerSize", oldDividerSize, 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.
  	 *
  	 * @param floatingDividers
  	 * @see #getFloatingDividers
  	 */
  	public void setFloatingDividers(final boolean floatingDividers) {
  		final boolean oldFloatingDividers = this.floatingDividers;
  		this.floatingDividers = floatingDividers;
  		firePCS("floatingDividers", oldFloatingDividers, floatingDividers);
  	}
  
  
  	/**
  	 * 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
  	 */
  	public void addLayoutComponent(final String name, final 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(final Component child) {
  		final String name = child.getName();
  		if (name != null) {
  			childMap.remove(name);
  		}
  	}
  
  
  	private Component childForNode(final Node node) {
  		if (node instanceof Leaf) {
  			final Leaf leaf = (Leaf) node;
  			final String name = leaf.getName();
  			return name != null ? childMap.get(name) : null;
  		}
  		return null;
  	}
  
  
  	private Dimension preferredComponentSize(final Node node) {
  		final Component child = childForNode(node);
  		return child != null ? child.getPreferredSize() : new Dimension(0, 0);
  
  	}
  
  
  	private Dimension preferredNodeSize(final Node root) {
  		if (root instanceof Leaf) {
  			return preferredComponentSize(root);
  		} else if (root instanceof Divider) {
  			final int dividerSize = getDividerSize();
  			return new Dimension(dividerSize, dividerSize);
  		} else {
  			final Split split = (Split) root;
  			final List<Node> splitChildren = split.getChildren();
  			int width = 0;
  			int height = 0;
  			if (split.isRowLayout()) {
  				for (final Node splitChild : splitChildren) {
  					final Dimension size = preferredNodeSize(splitChild);
  					width += size.width;
  					height = Math.max(height, size.height);
  				}
  			} else {
  				for (final Node splitChild : splitChildren) {
  					final Dimension size = preferredNodeSize(splitChild);
  					width = Math.max(width, size.width);
  					height += size.height;
  				}
  			}
  			return new Dimension(width, height);
  		}
  	}
  
  
  	private Dimension minimumNodeSize(final Node root) {
  		if (root instanceof Leaf) {
  			final Component child = childForNode(root);
  			return child != null ? child.getMinimumSize() : new Dimension(0, 0);
  		} else if (root instanceof Divider) {
  			final int dividerSize = getDividerSize();
  			return new Dimension(dividerSize, dividerSize);
  		} else {
  			final Split split = (Split) root;
  			final List<Node> splitChildren = split.getChildren();
  			int width = 0;
  			int height = 0;
  			if (split.isRowLayout()) {
  				for (final Node splitChild : splitChildren) {
  					final Dimension size = minimumNodeSize(splitChild);
  					width += size.width;
  					height = Math.max(height, size.height);
  				}
  			} else {
  				for (final Node splitChild : splitChildren) {
  					final Dimension size = minimumNodeSize(splitChild);
  					width = Math.max(width, size.width);
  					height += size.height;
  				}
  			}
  			return new Dimension(width, height);
  		}
  	}
  
  
  	private static Dimension sizeWithInsets(final Container parent, final Dimension size) {
  		final Insets insets = parent.getInsets();
  		final int width = size.width + insets.left + insets.right;
  		final int height = size.height + insets.top + insets.bottom;
  		return new Dimension(width, height);
  	}
  
  
  	public Dimension preferredLayoutSize(final Container parent) {
  		final Dimension size = preferredNodeSize(getModel());
  		return sizeWithInsets(parent, size);
  	}
  
  
  	public Dimension minimumLayoutSize(final Container parent) {
  		final Dimension size = minimumNodeSize(getModel());
  		return sizeWithInsets(parent, size);
  	}
  
  
  	private static Rectangle boundsWithYandHeight(final Rectangle bounds, final double y, final double height) {
  		final Rectangle r = new Rectangle();
  		r.setBounds((int) bounds.getX(), (int) y, (int) bounds.getWidth(), (int) height);
  		return r;
  	}
  
  
  	private static Rectangle boundsWithXandWidth(final Rectangle bounds, final double x, final double width) {
  		final Rectangle r = new Rectangle();
  		r.setBounds((int) x, (int) bounds.getY(), (int) width, (int) bounds.getHeight());
  		return r;
  	}
  
  
  	private static void minimizeSplitBounds(final Split split, final Rectangle bounds) {
  		final Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0);
  		final List<Node> splitChildren = split.getChildren();
  		final Node lastChild = splitChildren.get(splitChildren.size() - 1);
  		final Rectangle lastChildBounds = lastChild.getBounds();
  		if (split.isRowLayout()) {
  			final int lastChildMaxX = lastChildBounds.x + lastChildBounds.width;
  			splitBounds.add(lastChildMaxX, bounds.y + bounds.height);
  		} else {
  			final int lastChildMaxY = lastChildBounds.y + lastChildBounds.height;
  			splitBounds.add(bounds.x + bounds.width, lastChildMaxY);
  		}
  		split.setBounds(splitBounds);
  	}
  
  
  	private void layoutShrink(final Split split, final Rectangle bounds) {
  		final Rectangle splitBounds = split.getBounds();
  		final ListIterator<Node> splitChildren = split.getChildren().listIterator();
  		//final 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 (final Node splitChild : split.getChildren()) {
  				final int nodeWidth = splitChild.getBounds().width;
  				final int nodeMinWidth = Math.min(nodeWidth, minimumNodeSize(splitChild).width);
  				totalWidth += nodeWidth;
  				if (splitChild.getWeight() > 0.0) {
  					minWeightedWidth += nodeMinWidth;
  					totalWeightedWidth += nodeWidth;
  				}
  			}
  
  			double x = bounds.getX();
  			final double extraWidth = splitBounds.getWidth() - bounds.getWidth();
  			double availableWidth = extraWidth;
  			final boolean onlyShrinkWeightedComponents = totalWeightedWidth - minWeightedWidth > extraWidth;
  
  			while (splitChildren.hasNext()) {
  				final Node splitChild = splitChildren.next();
  				final Rectangle splitChildBounds = splitChild.getBounds();
  				final double minSplitChildWidth = minimumNodeSize(splitChild).getWidth();
  				final double splitChildWeight = onlyShrinkWeightedComponents ? splitChild.getWeight() : splitChildBounds.getWidth() / (double) totalWidth;
  
  				if (!splitChildren.hasNext()) {
  					final double newWidth = Math.max(minSplitChildWidth, bounds.getMaxX() - x);
  					final Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
  					layout2(splitChild, newSplitChildBounds);
  				} else if (availableWidth > 0.0 && splitChildWeight > 0.0) {
  					final double allocatedWidth = Math.rint(splitChildWeight * extraWidth);
  					final double oldWidth = splitChildBounds.getWidth();
  					final double newWidth = Math.max(minSplitChildWidth, oldWidth - allocatedWidth);
  					final Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
  					layout2(splitChild, newSplitChildBounds);
  					availableWidth -= oldWidth - splitChild.getBounds().getWidth();
  				} else {
  					final double existingWidth = splitChildBounds.getWidth();
  					final 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 (final Node splitChild : split.getChildren()) {
  				final int nodeHeight = splitChild.getBounds().height;
  				final int nodeMinHeight = Math.min(nodeHeight, minimumNodeSize(splitChild).height);
  				totalHeight += nodeHeight;
  				if (splitChild.getWeight() > 0.0) {
  					minWeightedHeight += nodeMinHeight;
  					totalWeightedHeight += nodeHeight;
  				}
  			}
  
  			double y = bounds.getY();
  			final double extraHeight = splitBounds.getHeight() - bounds.getHeight();
  			double availableHeight = extraHeight;
  			final boolean onlyShrinkWeightedComponents = totalWeightedHeight - minWeightedHeight > extraHeight;
  
  			while (splitChildren.hasNext()) {
  				final Node splitChild = splitChildren.next();
  				final Rectangle splitChildBounds = splitChild.getBounds();
  				final double minSplitChildHeight = minimumNodeSize(splitChild).getHeight();
  				final double splitChildWeight = onlyShrinkWeightedComponents ? splitChild.getWeight() : splitChildBounds.getHeight() / (double) totalHeight;
  
  				if (!splitChildren.hasNext()) {
  					final double oldHeight = splitChildBounds.getHeight();
  					final double newHeight = Math.max(minSplitChildHeight, bounds.getMaxY() - y);
  					final Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
  					layout2(splitChild, newSplitChildBounds);
  					availableHeight -= oldHeight - splitChild.getBounds().getHeight();
  				} else if (availableHeight > 0.0 && splitChildWeight > 0.0) {
  					final double allocatedHeight = Math.rint(splitChildWeight * extraHeight);
  					final double oldHeight = splitChildBounds.getHeight();
  					final double newHeight = Math.max(minSplitChildHeight, oldHeight - allocatedHeight);
  					final Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
  					layout2(splitChild, newSplitChildBounds);
  					availableHeight -= oldHeight - splitChild.getBounds().getHeight();
  				} else {
  					final double existingHeight = splitChildBounds.getHeight();
  					final 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);
  	}
  
  
  	private void layoutGrow(final Split split, final Rectangle bounds) {
  		final Rectangle splitBounds = split.getBounds();
  		final ListIterator<Node> splitChildren = split.getChildren().listIterator();
  		final 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();
  			final double extraWidth = bounds.getWidth() - splitBounds.getWidth();
  			double availableWidth = extraWidth;
  
  			while (splitChildren.hasNext()) {
  				final Node splitChild = splitChildren.next();
  				final Rectangle splitChildBounds = splitChild.getBounds();
  				final double splitChildWeight = splitChild.getWeight();
  
  				if (!splitChildren.hasNext()) {
  					final double newWidth = bounds.getMaxX() - x;
  					final Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
  					layout2(splitChild, newSplitChildBounds);
  				} else if (availableWidth > 0.0 && splitChildWeight > 0.0) {
  					final double allocatedWidth = splitChild.equals(lastWeightedChild) ? availableWidth : Math.rint(splitChildWeight * extraWidth);
  					final double newWidth = splitChildBounds.getWidth() + allocatedWidth;
  					final Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
  					layout2(splitChild, newSplitChildBounds);
  					availableWidth -= allocatedWidth;
  				} else {
  					final double existingWidth = splitChildBounds.getWidth();
  					final 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();
  			final double extraHeight = bounds.getMaxY() - splitBounds.getHeight();
  			double availableHeight = extraHeight;
  
  			while (splitChildren.hasNext()) {
  				final Node splitChild = splitChildren.next();
  				final Rectangle splitChildBounds = splitChild.getBounds();
  				final double splitChildWeight = splitChild.getWeight();
  
  				if (!splitChildren.hasNext()) {
  					final double newHeight = bounds.getMaxY() - y;
  					final Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
  					layout2(splitChild, newSplitChildBounds);
  				} else if (availableHeight > 0.0 && splitChildWeight > 0.0) {
  					final double allocatedHeight = splitChild.equals(lastWeightedChild) ? availableHeight : Math.rint(splitChildWeight * extraHeight);
  					final double newHeight = splitChildBounds.getHeight() + allocatedHeight;
  					final Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
  					layout2(splitChild, newSplitChildBounds);
  					availableHeight -= allocatedHeight;
  				} else {
  					final double existingHeight = splitChildBounds.getHeight();
  					final 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(final Node root, final Rectangle bounds) {
  		if (root instanceof Leaf) {
  			final 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) {
  			final Split split = (Split) root;
  			final 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(final Node root, final Rectangle bounds) {
  		if (root instanceof Leaf) {
  			root.setBounds(bounds);
  		} else if (root instanceof Split) {
  			final Split split = (Split) root;
  			final Iterator<Node> splitChildren = split.getChildren().iterator();
  			Rectangle childBounds;
  			final int dividerSize = getDividerSize();
  
  			/* 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()) {
  					final Node splitChild = splitChildren.next();
  					final Divider dividerChild = splitChildren.hasNext() ? (Divider) splitChildren.next() : null;
  
  					final double childWidth;
  					if (getFloatingDividers()) {
  						childWidth = preferredNodeSize(splitChild).getWidth();
  					} else {
  						if (dividerChild != null) {
  							childWidth = dividerChild.getBounds().getX() - x;
  						} else {
  							childWidth = split.getBounds().getMaxX() - x;
  						}
  					}
  					childBounds = boundsWithXandWidth(bounds, x, childWidth);
  					layout1(splitChild, childBounds);
  
  					if (getFloatingDividers() && dividerChild != null) {
  						final double dividerX = childBounds.getMaxX();
  						final Rectangle dividerBounds = boundsWithXandWidth(bounds, dividerX, dividerSize);
  						dividerChild.setBounds(dividerBounds);
  					}
  					if (dividerChild != null) {
  						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()) {
  					final Node splitChild = splitChildren.next();
  					final Divider dividerChild = splitChildren.hasNext() ? (Divider) splitChildren.next() : null;
  
  					final double childHeight;
  					if (getFloatingDividers()) {
  						childHeight = preferredNodeSize(splitChild).getHeight();
  					} else {
  						if (dividerChild != null) {
  							childHeight = dividerChild.getBounds().getY() - y;
  						} else {
  							childHeight = split.getBounds().getMaxY() - y;
  						}
  					}
  					childBounds = boundsWithYandHeight(bounds, y, childHeight);
  					layout1(splitChild, childBounds);
  
  					if (getFloatingDividers() && dividerChild != null) {
  						final double dividerY = childBounds.getMaxY();
  						final Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, dividerSize);
  						dividerChild.setBounds(dividerBounds);
  					}
  					if (dividerChild != null) {
  						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);
  		}
  	}
  
  
  	/**
  	 * The specified Node is either the wrong type or was configured
  	 * incorrectly.
  	 */
  	public static class InvalidLayoutException extends RuntimeException {
  
  		private static final long serialVersionUID = 0L;
  
  		private final transient Node node;
  
  
  		public InvalidLayoutException(final String msg, final Node node) {
  			super(msg);
  			this.node = node;
  		}
  
  
  		/** @return the invalid Node. */
  		public Node getNode() {
  			return node;
  		}
  	}
  
  
  	private static void throwInvalidLayout(final String msg, final Node node) {
  		throw new InvalidLayoutException(msg, node);
  	}
  
  
  	private static void checkLayout(final Node root) {
  		if (root instanceof Split) {
  			final Split split = (Split) root;
  			//if (split.getChildren().size() <= 2) {
  				//throwInvalidLayout("Split must have > 2 children", root);
  			//}
  			final Iterator<Node> splitChildren = split.getChildren().iterator();
  			double weight = 0.0;
  			while (splitChildren.hasNext()) {
  				final Node splitChild = splitChildren.next();
  				if (splitChild instanceof Divider) {
  					throwInvalidLayout("expected a Split or Leaf Node", splitChild);
  				}
  				if (splitChildren.hasNext()) {
  					final 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.
  	 */
  	public void layoutContainer(final Container parent) {
  		checkLayout(getModel());
  		final Insets insets = parent.getInsets();
  		final Dimension size = parent.getSize();
  		final int width = size.width - (insets.left + insets.right);
  		final int height = size.height - (insets.top + insets.bottom);
  		final Rectangle bounds = new Rectangle(insets.left, insets.top, width, height);
  		layout1(getModel(), bounds);
  		layout2(getModel(), bounds);
  	}
  
  
  	private static Divider dividerAt(final Node root, final int x, final int y) {
  		if (root instanceof Divider) {
  			final Divider divider = (Divider) root;
  			return divider.getBounds().contains(x, y) ? divider : null;
  		} else if (root instanceof Split) {
  			final Split split = (Split) root;
  			for (final Node child : split.getChildren()) {
  				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(final int x, final int y) {
  		return dividerAt(getModel(), x, y);
  	}
  
  
  	private static boolean nodeOverlapsRectangle(final Node node, final Rectangle r2) {
  		final 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 static List<Divider> dividersThatOverlap(final Node root, final Rectangle r) {
  		if (nodeOverlapsRectangle(root, r) && root instanceof Split) {
  			final List<Divider> dividers = new ArrayList<Divider>();
  			for (final 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 Iterable<Divider> dividersThatOverlap(final Rectangle r) {
  		if (r == null) {
  			throw new IllegalArgumentException("null Rectangle");
  		}
  		return dividersThatOverlap(getModel(), r);
  	}
  
  
  	/** Base class for the nodes that model a MultiSplitLayout. */
  	public abstract static class Node {
  
  		private Split parent;
  		private Rectangle bounds = new Rectangle();
  		private double weight;
  
  
  		/**
  		 * 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(final 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(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(final 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
  		 * @throws IllegalArgumentException if weight is not between 0.0 and 1.0
  		 * @see #getWeight
  		 * @see MultiSplitLayout#layoutContainer
  		 */
  		public void setWeight(final double weight) {
  			if (weight < 0.0 || weight > 1.0) {
  				throw new IllegalArgumentException("invalid weight");
  			}
  			this.weight = weight;
  		}
  
  
  		private Node siblingAtOffset(final int offset) {
  			final Split parent = getParent();
  			if (parent == null) {
  				return null;
  			}
  			final List<Node> siblings = parent.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);
  		}
  	}
  
  	/**
  	 * 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;
  
  
  		/**
  		 * 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(final 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);
  		}
  
  
  		/**
  		 * 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
  		 * @throws IllegalArgumentException if children is null
  		 * @see #getChildren
  		 */
  		public void setChildren(final List<Node> children) {
  			if (children == null) {
  				throw new IllegalArgumentException("children must be a non-null List");
  			}
  			for (final Node child : this.children) {
  				child.setParent(null);
  			}
  			this.children = new ArrayList<Node>(children);
  			for (final Node child : this.children) {
  				child.setParent(this);
  			}
  		}
  
  
  		/**
  		 * 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() {
  			final List<Node> children = getChildren();
  			Node weightedChild = null;
  			for (final Node child : children) {
  				if (child.getWeight() > 0.0) {
  					weightedChild = child;
  				}
  			}
  			return weightedChild;
  		}
  
  
  		@Override
  		public String toString() {
  			final StringBuilder sb = new StringBuilder();
  			sb.append("Split");
  			sb.append("{children=").append(children);
  			sb.append(", rowLayout=").append(rowLayout);
  			sb.append('}');
  			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(final 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(final String name) {
  			if (name == null) {
  				throw new IllegalArgumentException("name is null");
  			}
  			this.name = name;
  		}
  
  
  		@Override
  		public String toString() {
  			final StringBuilder sb = new StringBuilder();
  			sb.append("Leaf");
  			sb.append("{name='").append(name).append('\'');
  			sb.append('}');
  			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() {
  			final Split parent = getParent();
  			return parent != null && parent.isRowLayout();
  		}
  
  
  		/**
  		 * Dividers can't have a weight, they don't grow or shrink.
  		 *
  		 * @throws UnsupportedOperationException
  		 */
  		@Override
  		public void setWeight(final double weight) {
  			throw new UnsupportedOperationException();
  		}
  
  
  		@Override
  		public String toString() {
  			return "MultiSplitLayout.Divider " + getBounds().toString();
  		}
  	}
  
  
  	private static void throwParseException(final StreamTokenizer st, final String msg) throws Exception {
  		throw new Exception("MultiSplitLayout.parseModel Error: " + msg);
  	}
  
  
  	private static void parseAttribute(final String name, final StreamTokenizer st, final Node node) throws Exception {
  		if (st.nextToken() != '=') {
  			throwParseException(st, "expected '=' after " + name);
  		}
  		if ("WEIGHT".equalsIgnoreCase(name)) {
  			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 {
  					throwParseException(st, "can't specify name for " + node);
  				}
  			} else {
  				throwParseException(st, "invalid name");
  			}
  		} else {
  			throwParseException(st, "unrecognized attribute \"" + name + '"');
  		}
  	}
  
  
  	private static void addSplitChild(final Split parent, final Node child) {
  		final List<Node> children = new ArrayList<Node>(parent.getChildren());
  		if (children.isEmpty()) {
  			children.add(child);
  		} else {
  			children.add(new Divider());
  			children.add(child);
  		}
  		parent.setChildren(children);
  	}
  
  
  	private static void parseLeaf(final StreamTokenizer st, final Split parent) throws Exception {
  		final 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(final StreamTokenizer st, final Split parent) throws Exception {
  		int token;
  		while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
  			if (token == ')') {
  				break;
  			} else if (token == StreamTokenizer.TT_WORD) {
  				if ("WEIGHT".equalsIgnoreCase(st.sval)) {
  					parseAttribute(st.sval, st, parent);
  				} else {
  					addSplitChild(parent, new Leaf(st.sval));
  				}
  			} else if (token == '(') {
  				if (st.nextToken() != StreamTokenizer.TT_WORD) {
  					throwParseException(st, "invalid node type");
  				}
  				final String nodeType = st.sval.toUpperCase(Locale.ENGLISH);
  				if ("LEAF".equals(nodeType)) {
  					parseLeaf(st, parent);
  				} else if ("ROW".equals(nodeType) || "COLUMN".equals(nodeType)) {
  					final Split split = new Split();
  					split.setRowLayout("ROW".equals(nodeType));
  					addSplitChild(parent, split);
  					parseSplit(st, split);
  				} else {
  					throwParseException(st, "unrecognized node type '" + nodeType + '\'');
  				}
  			}
  		}
  	}
  
  
  	private static Node parseModel(final Reader r) {
  		final StreamTokenizer st = new StreamTokenizer(r);
  		try {
  			final Split root = new Split();
  			parseSplit(st, root);
  			return root.getChildren().get(0);
  		} catch (final Exception e) {
  			//noinspection UseOfSystemOutOrSystemErr
  			System.err.println(e);
  		} finally {
  			try {
  				r.close();
  			} catch (final 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/>
  	 * <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/>
  	 * <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/>
  	 * <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/>
  	 * <p/>
  	 * <p> This syntax is not intended for archiving or
  	 * configuration files .  It's just a convenience for
  	 * examples and tests.
  	 *
  	 * @param s
  	 * @return the Node root of a tree based on s.
  	 */
  	public static Node parseModel(final String s) {
  		return parseModel(new StringReader(s));
  	}
  
  
  	private static void printModel(final String indent, final Node root) {
  		if (root instanceof Split) {
  			final Split split = (Split) root;
  			//noinspection UseOfSystemOutOrSystemErr
  			System.out.println(indent + split);
  			for (final Node child : split.getChildren()) {
  				printModel(indent + "  ", child);
  			}
  		} else {
  			//noinspection UseOfSystemOutOrSystemErr
  			System.out.println(indent + root);
  		}
  	}
  
  
  	/** Print the tree with enough detail for simple debugging.
  	 * @param root*/
  	public static void printModel(final Node root) {
  		printModel("", root);
  	}
  }
  