Index: src/org/jdesktop/swingx/MultiSplitLayout.java
===================================================================
--- src/org/jdesktop/swingx/MultiSplitLayout.java (revision 0)
+++ src/org/jdesktop/swingx/MultiSplitLayout.java (revision 0)
@@ -0,0 +1,1351 @@
+/*
+ * $Id: MultiSplitLayout.java,v 1.15 2005/10/26 14:29:54 hansmuller Exp $
+ *
+ * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
+ * Santa Clara, California 95054, U.S.A. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.jdesktop.swingx;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import javax.swing.UIManager;
+
+
+/**
+ * The MultiSplitLayout layout manager recursively arranges its
+ * components in row and column groups called "Splits". Elements of
+ * the layout are separated by gaps called "Dividers". The overall
+ * layout is defined with a simple tree model whose nodes are
+ * instances of MultiSplitLayout.Split, MultiSplitLayout.Divider,
+ * and MultiSplitLayout.Leaf. Named Leaf nodes represent the space
+ * allocated to a component that was added with a constraint that
+ * matches the Leaf's name. Extra space is distributed
+ * among row/column siblings according to their 0.0 to 1.0 weight.
+ * If no weights are specified then the last sibling always gets
+ * all of the extra space, or space reduction.
+ *
+ *
+ * Although MultiSplitLayout can be used with any Container, it's
+ * the default layout manager for MultiSplitPane. MultiSplitPane
+ * supports interactively dragging the Dividers, accessibility,
+ * and other features associated with split panes.
+ *
+ *
+ * All properties in this class are bound: when a properties value
+ * is changed, all PropertyChangeListeners are fired.
+ *
+ * @author Hans Muller
+ * @see MultiSplitPane
+ */
+
+public class MultiSplitLayout implements LayoutManager {
+ private final Map childMap = new HashMap();
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+ private Node model;
+ private int dividerSize;
+ private boolean floatingDividers = true;
+
+ /**
+ * 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 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 = model;
+ this.model = model;
+ firePCS("model", oldModel, 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
+ */
+ 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", 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.
+ *
+ * @see #getFloatingDividers
+ */
+ public void setFloatingDividers(boolean floatingDividers) {
+ boolean oldFloatingDividers = this.floatingDividers;
+ this.floatingDividers = floatingDividers;
+ firePCS("floatingDividers", oldFloatingDividers, floatingDividers);
+ }
+
+
+ /**
+ * Add a component to this MultiSplitLayout. The
+ * name
should match the name property of the Leaf
+ * node that represents the bounds of child
. After
+ * layoutContainer() recomputes the bounds of all of the nodes in
+ * the model, it will set this child's bounds to the bounds of the
+ * Leaf node with name
. Note: if a component was already
+ * added with the same name, this method does not remove it from
+ * its parent.
+ *
+ * @param name identifies the Leaf node that defines the child's bounds
+ * @param child the component to be added
+ * @see #removeLayoutComponent
+ */
+ public void addLayoutComponent(String name, Component child) {
+ if (name == null) {
+ throw new IllegalArgumentException("name not specified");
+ }
+ childMap.put(name, child);
+ }
+
+ /**
+ * Removes the specified component from the layout.
+ *
+ * @param child the component to be removed
+ * @see #addLayoutComponent
+ */
+ public void removeLayoutComponent(Component child) {
+ String name = child.getName();
+ if (name != null) {
+ childMap.remove(name);
+ }
+ }
+
+ 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) {
+ Component child = childForNode(node);
+ return (child != null) ? child.getPreferredSize() : new Dimension(0, 0);
+
+ }
+
+ private Dimension minimumComponentSize(Node node) {
+ Component child = childForNode(node);
+ return (child != null) ? child.getMinimumSize() : new Dimension(0, 0);
+
+ }
+
+ private Dimension preferredNodeSize(Node root) {
+ if (root instanceof Leaf) {
+ return preferredComponentSize(root);
+ }
+ else if (root instanceof Divider) {
+ int dividerSize = getDividerSize();
+ return new Dimension(dividerSize, dividerSize);
+ }
+ else {
+ Split split = (Split)root;
+ List splitChildren = split.getChildren();
+ int width = 0;
+ int height = 0;
+ if (split.isRowLayout()) {
+ for(Node splitChild : splitChildren) {
+ Dimension size = preferredNodeSize(splitChild);
+ width += size.width;
+ height = Math.max(height, size.height);
+ }
+ }
+ else {
+ for(Node splitChild : splitChildren) {
+ Dimension size = preferredNodeSize(splitChild);
+ width = Math.max(width, size.width);
+ height += size.height;
+ }
+ }
+ return new Dimension(width, height);
+ }
+ }
+
+ private Dimension minimumNodeSize(Node root) {
+ if (root instanceof Leaf) {
+ Component child = childForNode(root);
+ return (child != null) ? child.getMinimumSize() : new Dimension(0, 0);
+ }
+ else if (root instanceof Divider) {
+ int dividerSize = getDividerSize();
+ return new Dimension(dividerSize, dividerSize);
+ }
+ else {
+ Split split = (Split)root;
+ List splitChildren = split.getChildren();
+ int width = 0;
+ int height = 0;
+ if (split.isRowLayout()) {
+ for(Node splitChild : splitChildren) {
+ Dimension size = minimumNodeSize(splitChild);
+ width += size.width;
+ height = Math.max(height, size.height);
+ }
+ }
+ else {
+ for(Node splitChild : splitChildren) {
+ Dimension size = minimumNodeSize(splitChild);
+ width = Math.max(width, size.width);
+ height += size.height;
+ }
+ }
+ return new Dimension(width, height);
+ }
+ }
+
+ private Dimension sizeWithInsets(Container parent, Dimension size) {
+ Insets insets = parent.getInsets();
+ int width = size.width + insets.left + insets.right;
+ int height = size.height + insets.top + insets.bottom;
+ return new Dimension(width, height);
+ }
+
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension size = preferredNodeSize(getModel());
+ return sizeWithInsets(parent, size);
+ }
+
+ public Dimension minimumLayoutSize(Container parent) {
+ Dimension size = minimumNodeSize(getModel());
+ return sizeWithInsets(parent, size);
+ }
+
+
+ private Rectangle boundsWithYandHeight(Rectangle bounds, double y, double height) {
+ Rectangle r = new Rectangle();
+ r.setBounds((int)(bounds.getX()), (int)y, (int)(bounds.getWidth()), (int)height);
+ return r;
+ }
+
+ private Rectangle boundsWithXandWidth(Rectangle bounds, double x, double width) {
+ Rectangle r = new Rectangle();
+ r.setBounds((int)x, (int)(bounds.getY()), (int)width, (int)(bounds.getHeight()));
+ return r;
+ }
+
+
+ private void minimizeSplitBounds(Split split, Rectangle bounds) {
+ Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0);
+ List splitChildren = split.getChildren();
+ Node lastChild = splitChildren.get(splitChildren.size() - 1);
+ Rectangle lastChildBounds = lastChild.getBounds();
+ if (split.isRowLayout()) {
+ int lastChildMaxX = lastChildBounds.x + lastChildBounds.width;
+ splitBounds.add(lastChildMaxX, bounds.y + bounds.height);
+ }
+ else {
+ int lastChildMaxY = lastChildBounds.y + lastChildBounds.height;
+ splitBounds.add(bounds.x + bounds.width, lastChildMaxY);
+ }
+ split.setBounds(splitBounds);
+ }
+
+
+ private void layoutShrink(Split split, Rectangle bounds) {
+ Rectangle splitBounds = split.getBounds();
+ ListIterator splitChildren = split.getChildren().listIterator();
+ Node lastWeightedChild = split.lastWeightedChild();
+
+ if (split.isRowLayout()) {
+ int totalWidth = 0; // sum of the children's widths
+ int minWeightedWidth = 0; // sum of the weighted childrens' min widths
+ int totalWeightedWidth = 0; // sum of the weighted childrens' widths
+ for(Node splitChild : split.getChildren()) {
+ int nodeWidth = splitChild.getBounds().width;
+ int 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();
+ Rectangle splitChildBounds = splitChild.getBounds();
+ double minSplitChildWidth = minimumNodeSize(splitChild).getWidth();
+ double splitChildWeight = (onlyShrinkWeightedComponents)
+ ? splitChild.getWeight()
+ : (splitChildBounds.getWidth() / (double)totalWidth);
+
+ if (!splitChildren.hasNext()) {
+ double newWidth = Math.max(minSplitChildWidth, bounds.getMaxX() - x);
+ Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
+ layout2(splitChild, newSplitChildBounds);
+ }
+ else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) {
+ double allocatedWidth = Math.rint(splitChildWeight * extraWidth);
+ double oldWidth = splitChildBounds.getWidth();
+ double 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()) {
+ int nodeHeight = splitChild.getBounds().height;
+ int 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();
+ Rectangle splitChildBounds = splitChild.getBounds();
+ double minSplitChildHeight = minimumNodeSize(splitChild).getHeight();
+ double splitChildWeight = (onlyShrinkWeightedComponents)
+ ? splitChild.getWeight()
+ : (splitChildBounds.getHeight() / (double)totalHeight);
+
+ if (!splitChildren.hasNext()) {
+ double oldHeight = splitChildBounds.getHeight();
+ double newHeight = Math.max(minSplitChildHeight, bounds.getMaxY() - y);
+ Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
+ layout2(splitChild, newSplitChildBounds);
+ availableHeight -= (oldHeight - splitChild.getBounds().getHeight());
+ }
+ else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
+ double allocatedHeight = Math.rint(splitChildWeight * extraHeight);
+ double oldHeight = splitChildBounds.getHeight();
+ double 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);
+ }
+
+
+ private void layoutGrow(Split split, Rectangle bounds) {
+ Rectangle splitBounds = split.getBounds();
+ ListIterator splitChildren = split.getChildren().listIterator();
+ Node lastWeightedChild = split.lastWeightedChild();
+
+ /* Layout the Split's child Nodes' along the X axis. The bounds
+ * of each child will have the same y coordinate and height as the
+ * layoutGrow() bounds argument. Extra width is allocated to the
+ * to each child with a non-zero weight:
+ * newWidth = currentWidth + (extraWidth * splitChild.getWeight())
+ * Any extraWidth "left over" (that's availableWidth in the loop
+ * below) is given to the last child. Note that Dividers always
+ * have a weight of zero, and they're never the last child.
+ */
+ if (split.isRowLayout()) {
+ double x = bounds.getX();
+ double extraWidth = bounds.getWidth() - splitBounds.getWidth();
+ double availableWidth = extraWidth;
+
+ while(splitChildren.hasNext()) {
+ Node splitChild = splitChildren.next();
+ Rectangle splitChildBounds = splitChild.getBounds();
+ double splitChildWeight = splitChild.getWeight();
+
+ if (!splitChildren.hasNext()) {
+ 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.getMaxY() - splitBounds.getHeight();
+ double availableHeight = extraHeight;
+
+ while(splitChildren.hasNext()) {
+ Node splitChild = splitChildren.next();
+ Rectangle splitChildBounds = splitChild.getBounds();
+ double splitChildWeight = splitChild.getWeight();
+
+ if (!splitChildren.hasNext()) {
+ double newHeight = bounds.getMaxY() - y;
+ Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
+ layout2(splitChild, newSplitChildBounds);
+ }
+ else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
+ double allocatedHeight = (splitChild.equals(lastWeightedChild))
+ ? availableHeight
+ : Math.rint(splitChildWeight * extraHeight);
+ double newHeight = splitChildBounds.getHeight() + allocatedHeight;
+ Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
+ layout2(splitChild, newSplitChildBounds);
+ availableHeight -= allocatedHeight;
+ }
+ else {
+ double existingHeight = splitChildBounds.getHeight();
+ Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight);
+ layout2(splitChild, newSplitChildBounds);
+ }
+ y = splitChild.getBounds().getMaxY();
+ }
+ }
+ }
+
+
+ /* Second pass of the layout algorithm: branch to layoutGrow/Shrink
+ * as needed.
+ */
+ private void layout2(Node root, Rectangle bounds) {
+ if (root instanceof Leaf) {
+ Component child = childForNode(root);
+ if (child != null) {
+ child.setBounds(bounds);
+ }
+ root.setBounds(bounds);
+ }
+ else if (root instanceof Divider) {
+ root.setBounds(bounds);
+ }
+ else if (root instanceof Split) {
+ Split split = (Split)root;
+ boolean grow = split.isRowLayout()
+ ? (split.getBounds().width <= bounds.width)
+ : (split.getBounds().height <= bounds.height);
+ if (grow) {
+ layoutGrow(split, bounds);
+ root.setBounds(bounds);
+ }
+ else {
+ layoutShrink(split, bounds);
+ // split.setBounds() called in layoutShrink()
+ }
+ }
+ }
+
+
+ /* First pass of the layout algorithm.
+ *
+ * If the Dividers are "floating" then set the bounds of each
+ * node to accomodate the preferred size of all of the
+ * Leaf's java.awt.Components. Otherwise, just set the bounds
+ * of each Leaf/Split node so that it's to the left of (for
+ * Split.isRowLayout() Split children) or directly above
+ * the Divider that follows.
+ *
+ * This pass sets the bounds of each Node in the layout model. It
+ * does not resize any of the parent Container's
+ * (java.awt.Component) children. That's done in the second pass,
+ * see layoutGrow() and layoutShrink().
+ */
+ private void layout1(Node root, Rectangle bounds) {
+ if (root instanceof Leaf) {
+ root.setBounds(bounds);
+ }
+ else if (root instanceof Split) {
+ Split split = (Split)root;
+ Iterator splitChildren = split.getChildren().iterator();
+ Rectangle childBounds = null;
+ int 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()) {
+ Node splitChild = splitChildren.next();
+ Divider dividerChild =
+ (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
+
+ double childWidth = 0.0;
+ 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)) {
+ double dividerX = childBounds.getMaxX();
+ 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()) {
+ Node splitChild = splitChildren.next();
+ Divider dividerChild =
+ (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
+
+ double childHeight = 0.0;
+ 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)) {
+ double dividerY = childBounds.getMaxY();
+ 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 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 splitChildren = split.getChildren().iterator();
+ double weight = 0.0;
+ while(splitChildren.hasNext()) {
+ Node splitChild = splitChildren.next();
+ if (splitChild instanceof Divider) {
+ 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.
+ */
+ public void layoutContainer(Container 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.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 dividersThatOverlap(Node root, Rectangle r) {
+ if (nodeOverlapsRectangle(root, r) && (root instanceof Split)) {
+ List dividers = new ArrayList();
+ 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 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 {
+ private Split parent = null;
+ private Rectangle bounds = new Rectangle();
+ private double weight = 0.0;
+
+ /**
+ * 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 new Rectangle(0,0,0,0)
.
+ *
+ * @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 parent = getParent();
+ if (parent == null) { return null; }
+ List 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 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(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 getChildren() {
+ return new ArrayList(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
+ * @see #getChildren
+ * @throws IllegalArgumentException if children is null
+ */
+ public void setChildren(List 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(children);
+ for(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() {
+ List children = getChildren();
+ Node weightedChild = null;
+ for(Node child : children) {
+ if (child.getWeight() > 0.0) {
+ weightedChild = child;
+ }
+ }
+ return weightedChild;
+ }
+
+ public String toString() {
+ int nChildren = getChildren().size();
+ StringBuffer sb = new StringBuffer("MultiSplitLayout.Split");
+ 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;
+ }
+
+ 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
+ */
+ public void setWeight(double weight) {
+ throw new UnsupportedOperationException();
+ }
+
+ 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 {
+ 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 children = new ArrayList(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 {
+ 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=myLeafName weight=myLeafWeight.
+ * Similarly, a Split's weight can be specified with
+ * weight=mySplitWeight.
+ *
+ * For example, the following expression generates
+ * a horizontal Split node with three children:
+ * the Leafs named left and right, and a Divider in
+ * between:
+ *
+ * (ROW (LEAF name=left) (LEAF name=right weight=1.0))
+ *
+ *
+ * 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:
+ *
+ * (ROW left (LEAF name=right weight=1.0))
+ *
+ *
+ * Here's a more complex example. One row with
+ * three elements, the first and last of which are columns
+ * with two leaves each:
+ *
+ * (ROW (COLUMN weight=0.5 left.top left.bottom)
+ * (LEAF name=middle)
+ * (COLUMN weight=0.5 right.top right.bottom))
+ *
+ *
+ *
+ * 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);
+ }
+}
Index: src/org/jdesktop/swingx/MultiSplitPane.java
===================================================================
--- src/org/jdesktop/swingx/MultiSplitPane.java (revision 0)
+++ src/org/jdesktop/swingx/MultiSplitPane.java (revision 0)
@@ -0,0 +1,396 @@
+/*
+ * $Id: MultiSplitPane.java,v 1.15 2005/10/26 14:29:54 hansmuller Exp $
+ *
+ * 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.Color;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleRole;
+import javax.swing.JPanel;
+import javax.swing.event.MouseInputAdapter;
+import org.jdesktop.swingx.MultiSplitLayout.Divider;
+import org.jdesktop.swingx.MultiSplitLayout.Node;
+
+/**
+ *
+ *
+ * All properties in this class are bound: when a properties value
+ * is changed, all PropertyChangeListeners are fired.
+ *
+ * @author Hans Muller
+ */
+public class MultiSplitPane extends JPanel {
+ private AccessibleContext accessibleContext = null;
+ private boolean continuousLayout = true;
+ private DividerPainter dividerPainter = new DefaultDividerPainter();
+
+ /**
+ * Creates a MultiSplitPane with it's LayoutManager set to
+ * to an empty MultiSplitLayout.
+ */
+ public MultiSplitPane() {
+ super(new MultiSplitLayout());
+ InputHandler inputHandler = new InputHandler();
+ addMouseListener(inputHandler);
+ addMouseMotionListener(inputHandler);
+ addKeyListener(inputHandler);
+ setFocusable(true);
+ }
+
+ /**
+ * A convenience method that returns the layout manager cast
+ * to MutliSplitLayout.
+ *
+ * @return this MultiSplitPane's layout manager
+ * @see java.awt.Container#getLayout
+ * @see #setModel
+ */
+ public final MultiSplitLayout getMultiSplitLayout() {
+ return (MultiSplitLayout)getLayout();
+ }
+
+ /**
+ * A convenience method that sets the MultiSplitLayout model.
+ * Equivalent to getMultiSplitLayout.setModel(model)
+ *
+ * @param model the root of the MultiSplitLayout model
+ * @see #getMultiSplitLayout
+ * @see MultiSplitLayout#setModel
+ */
+ public final void setModel(Node model) {
+ getMultiSplitLayout().setModel(model);
+ }
+
+ /**
+ * A convenience method that sets the MultiSplitLayout dividerSize
+ * property. Equivalent to
+ * getMultiSplitLayout().setDividerSize(newDividerSize)
.
+ *
+ * @param dividerSize the value of the dividerSize property
+ * @see #getMultiSplitLayout
+ * @see MultiSplitLayout#setDividerSize
+ */
+ public final void setDividerSize(int dividerSize) {
+ getMultiSplitLayout().setDividerSize(dividerSize);
+ }
+
+ /**
+ * Sets the value of the continuousLayout
property.
+ * If true, then the layout is revalidated continuously while
+ * a divider is being moved. The default value of this property
+ * is true.
+ *
+ * @param continuousLayout value of the continuousLayout property
+ * @see #isContinuousLayout
+ */
+ public void setContinuousLayout(boolean continuousLayout) {
+ boolean oldContinuousLayout = continuousLayout;
+ this.continuousLayout = continuousLayout;
+ firePropertyChange("continuousLayout", oldContinuousLayout, continuousLayout);
+ }
+
+ /**
+ * Returns true if dragging a divider only updates
+ * the layout when the drag gesture ends (typically, when the
+ * mouse button is released).
+ *
+ * @return the value of the continuousLayout
property
+ * @see #setContinuousLayout
+ */
+ public boolean isContinuousLayout() {
+ return continuousLayout;
+ }
+
+ /**
+ * Returns the Divider that's currently being moved, typically
+ * because the user is dragging it, or null.
+ *
+ * @return the Divider that's being moved or null.
+ */
+ public Divider activeDivider() {
+ return dragDivider;
+ }
+
+ /**
+ * Draws a single Divider. Typically used to specialize the
+ * way the active Divider is painted.
+ *
+ * @see #getDividerPainter
+ * @see #setDividerPainter
+ */
+ public static abstract class DividerPainter {
+ /**
+ * Paint a single Divider.
+ *
+ * @param g the Graphics object to paint with
+ * @param divider the Divider to paint
+ */
+ public abstract void paint(Graphics g, Divider divider);
+ }
+
+ private class DefaultDividerPainter extends DividerPainter {
+ public void paint(Graphics g, Divider divider) {
+ if ((divider == activeDivider()) && !isContinuousLayout()) {
+ Graphics2D g2d = (Graphics2D)g;
+ g2d.setColor(Color.black);
+ g2d.fill(divider.getBounds());
+ }
+ }
+ }
+
+ /**
+ * The DividerPainter that's used to paint Dividers on this MultiSplitPane.
+ * This property may be null.
+ *
+ * @return the value of the dividerPainter Property
+ * @see #setDividerPainter
+ */
+ public DividerPainter getDividerPainter() {
+ return dividerPainter;
+ }
+
+ /**
+ * Sets the DividerPainter that's used to paint Dividers on this
+ * MultiSplitPane. The default DividerPainter only draws
+ * the activeDivider (if there is one) and then, only if
+ * continuousLayout is false. The value of this property is
+ * used by the paintChildren method: Dividers are painted after
+ * the MultiSplitPane's children have been rendered so that
+ * the activeDivider can appear "on top of" the children.
+ *
+ * @param dividerPainter the value of the dividerPainter property, can be null
+ * @see #paintChildren
+ * @see #activeDivider
+ */
+ public void setDividerPainter(DividerPainter dividerPainter) {
+ this.dividerPainter = dividerPainter;
+ }
+
+ /**
+ * Uses the DividerPainter (if any) to paint each Divider that
+ * overlaps the clip Rectangle. This is done after the call to
+ * super.paintChildren()
so that Dividers can be
+ * rendered "on top of" the children.
+ *
+ * {@inheritDoc}
+ */
+ protected void paintChildren(Graphics g) {
+ super.paintChildren(g);
+ DividerPainter dp = getDividerPainter();
+ Rectangle clipR = g.getClipBounds();
+ if ((dp != null) && (clipR != null)) {
+ Graphics dpg = g.create();
+ try {
+ MultiSplitLayout msl = getMultiSplitLayout();
+ for(Divider divider : msl.dividersThatOverlap(clipR)) {
+ dp.paint(dpg, divider);
+ }
+ }
+ finally {
+ dpg.dispose();
+ }
+ }
+ }
+
+ private boolean dragUnderway = false;
+ private MultiSplitLayout.Divider dragDivider = null;
+ private Rectangle initialDividerBounds = null;
+ private boolean oldFloatingDividers = true;
+ private int dragOffsetX = 0;
+ private int dragOffsetY = 0;
+ private int dragMin = -1;
+ private int dragMax = -1;
+
+ private void startDrag(int mx, int my) {
+ requestFocusInWindow();
+ MultiSplitLayout msl = getMultiSplitLayout();
+ MultiSplitLayout.Divider divider = msl.dividerAt(mx, my);
+ if (divider != null) {
+ MultiSplitLayout.Node prevNode = divider.previousSibling();
+ MultiSplitLayout.Node nextNode = divider.nextSibling();
+ if ((prevNode == null) || (nextNode == null)) {
+ dragUnderway = false;
+ }
+ else {
+ initialDividerBounds = divider.getBounds();
+ dragOffsetX = mx - initialDividerBounds.x;
+ dragOffsetY = my - initialDividerBounds.y;
+ dragDivider = divider;
+ Rectangle prevNodeBounds = prevNode.getBounds();
+ Rectangle nextNodeBounds = nextNode.getBounds();
+ if (dragDivider.isVertical()) {
+ dragMin = prevNodeBounds.x;
+ dragMax = nextNodeBounds.x + nextNodeBounds.width;
+ dragMax -= dragDivider.getBounds().width;
+ }
+ else {
+ dragMin = prevNodeBounds.y;
+ dragMax = nextNodeBounds.y + nextNodeBounds.height;
+ dragMax -= dragDivider.getBounds().height;
+ }
+ oldFloatingDividers = getMultiSplitLayout().getFloatingDividers();
+ getMultiSplitLayout().setFloatingDividers(false);
+ dragUnderway = true;
+ }
+ }
+ else {
+ dragUnderway = false;
+ }
+ }
+
+ private void repaintDragLimits() {
+ Rectangle damageR = dragDivider.getBounds();
+ if (dragDivider.isVertical()) {
+ damageR.x = dragMin;
+ damageR.width = dragMax - dragMin;
+ }
+ else {
+ damageR.y = dragMin;
+ damageR.height = dragMax - dragMin;
+ }
+ repaint(damageR);
+ }
+
+ private void updateDrag(int mx, int my) {
+ if (!dragUnderway) {
+ return;
+ }
+ Rectangle oldBounds = dragDivider.getBounds();
+ Rectangle bounds = new Rectangle(oldBounds);
+ if (dragDivider.isVertical()) {
+ bounds.x = mx - dragOffsetX;
+ bounds.x = Math.max(bounds.x, dragMin);
+ bounds.x = Math.min(bounds.x, dragMax);
+ }
+ else {
+ bounds.y = my - dragOffsetY;
+ bounds.y = Math.max(bounds.y, dragMin);
+ bounds.y = Math.min(bounds.y, dragMax);
+ }
+ dragDivider.setBounds(bounds);
+ if (isContinuousLayout()) {
+ revalidate();
+ repaintDragLimits();
+ }
+ else {
+ repaint(oldBounds.union(bounds));
+ }
+ }
+
+ private void clearDragState() {
+ dragDivider = null;
+ initialDividerBounds = null;
+ oldFloatingDividers = true;
+ dragOffsetX = dragOffsetY = 0;
+ dragMin = dragMax = -1;
+ dragUnderway = false;
+ }
+
+ private void finishDrag(int x, int y) {
+ if (dragUnderway) {
+ clearDragState();
+ if (!isContinuousLayout()) {
+ revalidate();
+ repaint();
+ }
+ }
+ }
+
+ private void cancelDrag() {
+ if (dragUnderway) {
+ dragDivider.setBounds(initialDividerBounds);
+ getMultiSplitLayout().setFloatingDividers(oldFloatingDividers);
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ repaint();
+ revalidate();
+ clearDragState();
+ }
+ }
+
+ private void updateCursor(int x, int y, boolean show) {
+ if (dragUnderway) {
+ return;
+ }
+ int cursorID = Cursor.DEFAULT_CURSOR;
+ if (show) {
+ MultiSplitLayout.Divider divider = getMultiSplitLayout().dividerAt(x, y);
+ if (divider != null) {
+ cursorID = (divider.isVertical()) ?
+ Cursor.E_RESIZE_CURSOR :
+ Cursor.N_RESIZE_CURSOR;
+ }
+ }
+ setCursor(Cursor.getPredefinedCursor(cursorID));
+ }
+
+
+ private class InputHandler extends MouseInputAdapter implements KeyListener {
+
+ public void mouseEntered(MouseEvent e) {
+ updateCursor(e.getX(), e.getY(), true);
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ updateCursor(e.getX(), e.getY(), true);
+ }
+
+ public void mouseExited(MouseEvent e) {
+ updateCursor(e.getX(), e.getY(), false);
+ }
+
+ public void mousePressed(MouseEvent e) {
+ startDrag(e.getX(), e.getY());
+ }
+ public void mouseReleased(MouseEvent e) {
+ finishDrag(e.getX(), e.getY());
+ }
+ public void mouseDragged(MouseEvent e) {
+ updateDrag(e.getX(), e.getY());
+ }
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ cancelDrag();
+ }
+ }
+ public void keyReleased(KeyEvent e) { }
+ public void keyTyped(KeyEvent e) { }
+ }
+
+ public AccessibleContext getAccessibleContext() {
+ if( accessibleContext == null ) {
+ accessibleContext = new AccessibleMultiSplitPane();
+ }
+ return accessibleContext;
+ }
+
+ protected class AccessibleMultiSplitPane extends AccessibleJPanel {
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.SPLIT_PANE;
+ }
+ }
+}
Index: src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java (working copy)
@@ -84,7 +84,7 @@
public SelectionListDialog() {
super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
- Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
+ Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
selectionHistory = new LinkedList>();
popupMenu = new JPopupMenu();
Index: src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java (working copy)
@@ -33,6 +33,7 @@
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.actions.HelpAction.Helpful;
+import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Shortcut;
@@ -43,43 +44,13 @@
*
*/
public class ToggleDialog extends JPanel implements Helpful {
-// private static final Logger logger = Logger.getLogger(ToggleDialog.class.getName());
-
- /**
- * The action to toggle the visibility state of this toggle dialog.
- *
- * Emits {@see PropertyChangeEvent}s for the property selected:
- *
- * - true, if the dialog is currently visible
- * - false, if the dialog is currently invisible
- *
- *
- */
- public final class ToggleDialogAction extends JosmAction {
- private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut, String prefname) {
- super(name, iconName, tooltip, shortcut, false);
- }
-
- public void actionPerformed(ActionEvent e) {
- toggleVisibility();
- }
-
- public void toggleVisibility() {
- if (isShowing) {
- hideDialog();
- } else {
- showDialog();
- }
- }
- }
-
- /**
- * The action to toggle this dialog.
- */
+ /** The action to toggle this dialog */
private ToggleDialogAction toggleAction;
private String preferencePrefix;
- private JPanel parent;
+ /** DialogsPanel that manages all ToggleDialogs */
+ private DialogsPanel dialogsPanel;
+
private TitleBar titleBar;
private String title;
@@ -100,12 +71,21 @@
/** the preferred height if the toggle dialog is expanded */
private int preferredHeight;
+
/** the label in the title bar which shows whether the toggle dialog is expanded or collapsed */
private JLabel lblMinimized;
+
/** the JDialog displaying the toggle dialog as undocked dialog */
private JDialog detachedDialog;
/**
+ * Constructor
+ * (see below)
+ */
+ public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
+ this(name, iconName, tooltip, shortcut, preferredHeight, false);
+ }
+ /**
* Constructor
*
* @param name the name of the dialog
@@ -113,23 +93,12 @@
* @param tooltip the tool tip
* @param shortcut the shortcut
* @param preferredHeight the preferred height for the dialog
+ * @param defShow if the dialog should be shown by default, if there is no preference
*/
- public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
+ public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow) {
super(new BorderLayout());
this.preferencePrefix = iconName;
- init(name, iconName, tooltip, shortcut, preferredHeight);
- }
- /**
- * Initializes the toggle dialog
- *
- * @param name
- * @param iconName
- * @param tooltip
- * @param shortcut
- * @param preferredHeight
- */
- private void init(String name, String iconName, String tooltip, Shortcut shortcut, final int preferredHeight) {
/** Use the full width of the parent element */
setPreferredSize(new Dimension(0, preferredHeight));
/** Override any minimum sizes of child elements so the user can resize freely */
@@ -146,121 +115,76 @@
titleBar = new TitleBar(name, iconName);
add(titleBar, BorderLayout.NORTH);
- setVisible(false);
setBorder(BorderFactory.createEtchedBorder());
-
+
+ isShowing = Main.pref.getBoolean(preferencePrefix+".visible", defShow);
isDocked = Main.pref.getBoolean(preferencePrefix+".docked", true);
isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
}
/**
- * Sets the visibility of all components in this toggle dialog, except the title bar
+ * The action to toggle the visibility state of this toggle dialog.
*
- * @param visible true, if the components should be visible; false otherwise
+ * Emits {@see PropertyChangeEvent}s for the property selected:
+ *
+ * - true, if the dialog is currently visible
+ * - false, if the dialog is currently invisible
+ *
+ *
*/
- protected void setContentVisible(boolean visible) {
- Component comps[] = getComponents();
- for(int i=0; i -1) {
- parent.remove(idx);
- if (idx >= parent.getComponentCount()) {
- parent.add(ToggleDialog.this);
- } else {
- parent.add(ToggleDialog.this,idx);
- }
- } else {
- parent.add(ToggleDialog.this);
- }
- parent.validate();
-
- if(Main.pref.getBoolean(preferencePrefix+".visible")) {
- setVisible(true);
- } else {
- setVisible(false);
- }
titleBar.setVisible(true);
- isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
- if (isCollapsed) {
- collapse();
- } else {
- expand();
- }
- isDocked = true;
- Main.pref.put(preferencePrefix+".docked", isDocked);
+ setIsDocked(true);
}
/**
@@ -269,85 +193,52 @@
*/
protected void detach() {
setContentVisible(true);
- setVisible(true);
- // replace the toggle dialog by an invisible place holder. Makes sure
- // we can place the toggle dialog where it was when it becomes docked
- // again.
- //
- if (parent != null) {
- int idx = getDialogPosition();
- if (idx > -1) {
- JPanel placeHolder = new JPanel();
- placeHolder.setName(this.getName());
- placeHolder.setVisible(false);
- parent.add(placeHolder,idx);
- }
- parent.remove(ToggleDialog.this);
- }
-
-
+ this.setVisible(true);
titleBar.setVisible(false);
detachedDialog = new DetachedDialog();
detachedDialog.setVisible(true);
- refreshToggleDialogsView();
- isDocked = false;
- Main.pref.put(preferencePrefix+".docked", isDocked);
+ setIsDocked(false);
}
/**
- * Hides the dialog
+ * Collapses the toggle dialog to the title bar only
+ *
*/
- public void hideDialog() {
- closeDetachedDialog();
- setVisible(false);
- isShowing = false;
- Main.pref.put(preferencePrefix+".visible", false);
- refreshToggleDialogsView();
- toggleAction.putValue("selected", false);
+ public void collapse() {
+ setContentVisible(false);
+ setIsCollapsed(true);
+ setPreferredSize(new Dimension(0,20));
+ setMaximumSize(new Dimension(Integer.MAX_VALUE,20));
+ setMinimumSize(new Dimension(Integer.MAX_VALUE,20));
+ lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
}
/**
- * Replies true if this dialog is showing either as docked or as detached dialog
+ * Expands the toggle dialog
*/
- public boolean isDialogShowing() {
- return this.isShowing;
+ protected void expand() {
+ setContentVisible(true);
+ setIsCollapsed(false);
+ setPreferredSize(new Dimension(0,preferredHeight));
+ setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+ lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
}
/**
- * Shows the dialog
+ * Sets the visibility of all components in this toggle dialog, except the title bar
+ *
+ * @param visible true, if the components should be visible; false otherwise
*/
- public void showDialog() {
- if (!isDocked) {
- detach();
- } else {
- dock();
- if (!isCollapsed) {
- expand();
- setVisible(true);
- refreshToggleDialogsView();
- } else {
- setVisible(true);
- refreshToggleDialogsView();
+ protected void setContentVisible(boolean visible) {
+ Component comps[] = getComponents();
+ for(int i=0; i allDialogs = new ArrayList();
+ protected MultiSplitPane mSpltPane = new MultiSplitPane();
+ final protected int DIVIDER_SIZE = 5;
+
+ /**
+ * Panels that are added to the multisplitpane.
+ */
+ private List panels = new ArrayList();
+
+ private boolean initialized = false;
+ public void initialize(List allDialogs) {
+ if (initialized) {
+ throw new IllegalStateException();
+ }
+ initialized = true;
+ this.allDialogs = allDialogs;
+
+ for (Integer i=0; i < allDialogs.size(); ++i) {
+ final ToggleDialog dlg = allDialogs.get(i);
+ dlg.setDialogsPanel(this);
+ dlg.setVisible(false);
+ }
+ for (int i=0; i < allDialogs.size() + 1; ++i) {
+ final JPanel p = new JPanel() {
+ /**
+ * Honoured by the MultiSplitPaneLayout when the
+ * entire Window is resized.
+ */
+ public Dimension getMinimumSize() {
+ return new Dimension(0, 40);
+ }
+ };
+ p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+ p.setVisible(false);
+
+ mSpltPane.add(p, "L"+i);
+ panels.add(p);
+ }
+
+ for (Integer i=0; i < allDialogs.size(); ++i) {
+ final ToggleDialog dlg = allDialogs.get(i);
+ if (dlg.isDialogShowing()) {
+ dlg.showDialog();
+ if (dlg.isDialogInCollapsedView()) {
+ dlg.collapse();
+ }
+ } else {
+ dlg.hideDialog();
+ }
+ }
+ this.add(mSpltPane);
+ reconstruct(Action.ELEMENT_SHRINKS, null);
+ }
+
+ /**
+ * What action was performed to trigger the reconstruction
+ */
+ public enum Action {
+ INVISIBLE_TO_DEFAULT,
+ COLLAPSED_TO_DEFAULT,
+ /* INVISIBLE_TO_COLLAPSED, does not happen */
+ ELEMENT_SHRINKS /* else. (Remaining elements have more space.) */
+ };
+ /**
+ * Reconstruct the view, if the configurations of dialogs has changed.
+ * @param action what happened, so the reconstruction is necessary
+ * @param triggeredBy the dialog that caused the reconstruction
+ */
+ public void reconstruct(Action action, ToggleDialog triggeredBy) {
+
+ final int N = allDialogs.size();
+
+ /**
+ * reset the panels
+ */
+ for (int i=0; i < N; ++i) {
+ final JPanel p = panels.get(i);
+ p.removeAll();
+ p.setVisible(false);
+ }
+
+ /**
+ * Add the elements to their respective panel.
+ *
+ * Each panel contains one dialog in default view and zero or more
+ * collapsed dialogs on top of it. The last panel is an exception
+ * as it can have collapsed dialogs at the bottom as well.
+ * If there are no dialogs in default view, show the collapsed ones
+ * in the last panel anyway.
+ */
+ int k = N-1; // index of the current Panel (start with last one)
+ JPanel p = panels.get(k); // current Panel
+ k = -1; // indicates that the current Panel index is N-1, but no default-view-Dialog was added to this Panel yet.
+ for (int i=N-1; i >= 0 ; --i) {
+ final ToggleDialog dlg = allDialogs.get(i);
+ if (dlg.isDialogInDefaultView()) {
+ if (k == -1) {
+ k = N-1;
+ } else {
+ --k;
+ p = panels.get(k);
+ }
+ p.add(dlg, 0);
+ p.setVisible(true);
+ }
+ else if (dlg.isDialogInCollapsedView()) {
+ p.add(dlg, 0);
+ p.setVisible(true);
+ }
+ }
+
+ if (k == -1) {
+ k = N-1;
+ }
+ final int numPanels = N - k;
+
+ /**
+ * Determine the panel geometry
+ */
+ if (action == Action.ELEMENT_SHRINKS) {
+ for (int i=0; i ch = new ArrayList();
+
+ for (int i = k; i <= N-1; ++i) {
+ if (i != k) {
+ ch.add(new Divider());
+ }
+ Leaf l = new Leaf("L"+i);
+ l.setWeight(1.0 / numPanels);
+ ch.add(l);
+ }
+
+ if (numPanels == 1) {
+ Node model = ch.get(0);
+ mSpltPane.getMultiSplitLayout().setModel(model);
+ } else {
+ Split model = new Split();
+ model.setRowLayout(false);
+ model.setChildren(ch);
+ mSpltPane.getMultiSplitLayout().setModel(model);
+ }
+
+ mSpltPane.getMultiSplitLayout().setDividerSize(DIVIDER_SIZE);
+ mSpltPane.getMultiSplitLayout().setFloatingDividers(true);
+ mSpltPane.revalidate();
+ }
+
+ public void destroy() {
+ for (ToggleDialog t : allDialogs) {
+ t.closeDetachedDialog();
+ }
+ }
+
+ /**
+ * Replies the instance of a toggle dialog of type type
managed by this
+ * map frame
+ *
+ * @param
+ * @param type the class of the toggle dialog, i.e. UserListDialog.class
+ * @return the instance of a toggle dialog of type type
managed by this
+ * map frame; null, if no such dialog exists
+ *
+ */
+ public T getToggleDialog(Class type) {
+ for (ToggleDialog td : allDialogs) {
+ if (type.isInstance(td))
+ return type.cast(td);
+ }
+ return null;
+ }
+}
Index: src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java (working copy)
@@ -28,7 +28,7 @@
public CommandStackDialog(final MapFrame mapFrame) {
super(tr("Command Stack"), "commandstack", tr("Open a list of all commands (undo buffer)."),
- Shortcut.registerShortcut("subwindow:commandstack", tr("Toggle: {0}", tr("Command Stack")), KeyEvent.VK_O, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 100);
+ Shortcut.registerShortcut("subwindow:commandstack", tr("Toggle: {0}", tr("Command Stack")), KeyEvent.VK_O, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 100, true);
Main.main.undoRedo.listenerCommands.add(this);
tree.setRootVisible(false);
Index: src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java (working copy)
@@ -143,7 +143,7 @@
*/
protected LayerListDialog(MapFrame mapFrame) {
super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
- Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100);
+ Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true);
// create the models
//
Index: src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java (working copy)
@@ -453,7 +453,7 @@
public PropertiesDialog(MapFrame mapFrame) {
super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
- Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
+ Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
// setting up the properties table
propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
Index: src/org/openstreetmap/josm/gui/MapFrame.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapFrame.java (revision 2186)
+++ src/org/openstreetmap/josm/gui/MapFrame.java (working copy)
@@ -7,6 +7,7 @@
import java.awt.Container;
import java.awt.Dimension;
import java.util.ArrayList;
+import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.Action;
@@ -28,6 +29,7 @@
import org.openstreetmap.josm.actions.mapmode.ZoomAction;
import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
+import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
import org.openstreetmap.josm.gui.dialogs.FilterDialog;
import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
@@ -74,8 +76,8 @@
* The panel list of all toggle dialog icons. To add new toggle dialog actions, use addToggleDialog
* instead of adding directly to this list.
*/
- private JPanel toggleDialogs = new JPanel();
- private ArrayList allDialogs = new ArrayList();
+ private List allDialogs = new ArrayList();
+ private DialogsPanel dialogsPanel = new DialogsPanel();
public final ButtonGroup toolGroup = new ButtonGroup();
@@ -106,7 +108,7 @@
toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
- mapView, toggleDialogs);
+ mapView, dialogsPanel);
/**
* All additional space goes to the mapView
@@ -129,10 +131,10 @@
add(splitPane, BorderLayout.CENTER);
- toggleDialogs.setLayout(new BoxLayout(toggleDialogs, BoxLayout.Y_AXIS));
- toggleDialogs.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
+ dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
+ dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
- toggleDialogs.setMinimumSize(new Dimension(24, 0));
+ dialogsPanel.setMinimumSize(new Dimension(24, 0));
mapView.setMinimumSize(new Dimension(10,0));
toolBarToggle.setFloatable(false);
@@ -172,9 +174,7 @@
* Delegates the call to all Destroyables within this component (e.g. MapModes)
*/
public void destroy() {
- for (ToggleDialog t : allDialogs) {
- t.closeDetachedDialog();
- }
+ dialogsPanel.destroy();
for (int i = 0; i < toolBarActions.getComponentCount(); ++i)
if (toolBarActions.getComponent(i) instanceof Destroyable) {
((Destroyable)toolBarActions).destroy();
@@ -202,18 +202,8 @@
/**
* Open all ToggleDialogs that have their preferences property set. Close all others.
*/
- public void setVisibleDialogs() {
- toggleDialogs.removeAll();
- for (ToggleDialog dialog: allDialogs) {
- dialog.setVisible(false);
- toggleDialogs.add(dialog);
- dialog.setParent(toggleDialogs);
- if (Main.pref.getBoolean(dialog.getPreferencePrefix()+".visible")) {
- dialog.showDialog();
- } else {
- dialog.hideDialog();
- }
- }
+ public void initializeDialogsPane() {
+ dialogsPanel.initialize(allDialogs);
}
/**
@@ -243,8 +233,6 @@
}
}
-
-
/**
* Change the operating map mode for the view. Will call unregister on the
* old MapMode and register on the new one.
@@ -298,17 +286,13 @@
*
*/
public T getToggleDialog(Class type) {
- for (ToggleDialog td : allDialogs) {
- if (type.isInstance(td))
- return type.cast(td);
- }
- return null;
+ return dialogsPanel.getToggleDialog(type);
}
/**
* Returns the current width of the (possibly resized) toggle dialog area
*/
public int getToggleDlgWidth() {
- return toggleDialogs.getWidth();
+ return dialogsPanel.getWidth();
}
}
Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java (revision 2186)
+++ src/org/openstreetmap/josm/Main.java (working copy)
@@ -219,7 +219,7 @@
setMapFrame(mapFrame);
mapFrame.selectMapMode((MapMode)mapFrame.getDefaultButtonAction());
mapFrame.setVisible(true);
- mapFrame.setVisibleDialogs();
+ mapFrame.initializeDialogsPane();
// bootstrapping problem: make sure the layer list dialog is going to
// listen to change events of the very first layer
//
Index: src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- src/org/openstreetmap/josm/data/Preferences.java (revision 2186)
+++ src/org/openstreetmap/josm/data/Preferences.java (working copy)
@@ -441,10 +441,6 @@
public final void resetToDefault(){
properties.clear();
- put("layerlist.visible", true);
- put("propertiesdialog.visible", true);
- put("selectionlist.visible", true);
- put("commandstack.visible", true);
if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") == -1) {
put("laf", "javax.swing.plaf.metal.MetalLookAndFeel");
} else {