Ticket #3550: split-vertical.patch

File split-vertical.patch, 96.8 KB (added by bastiK, 17 years ago)

lib included - should compile

  • src/org/jdesktop/swingx/MultiSplitLayout.java

     
     1/*
     2 * $Id: MultiSplitLayout.java,v 1.15 2005/10/26 14:29:54 hansmuller Exp $
     3 *
     4 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
     5 * Santa Clara, California 95054, U.S.A. All rights reserved.
     6 *
     7 * This library is free software; you can redistribute it and/or
     8 * modify it under the terms of the GNU Lesser General Public
     9 * License as published by the Free Software Foundation; either
     10 * version 2.1 of the License, or (at your option) any later version.
     11 *
     12 * This library is distributed in the hope that it will be useful,
     13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15 * Lesser General Public License for more details.
     16 *
     17 * You should have received a copy of the GNU Lesser General Public
     18 * License along with this library; if not, write to the Free Software
     19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 */
     21
     22package org.jdesktop.swingx;
     23
     24import java.awt.Component;
     25import java.awt.Container;
     26import java.awt.Dimension;
     27import java.awt.Insets;
     28import java.awt.LayoutManager;
     29import java.awt.Rectangle;
     30import java.beans.PropertyChangeListener;
     31import java.beans.PropertyChangeSupport;
     32import java.io.IOException;
     33import java.io.Reader;
     34import java.io.StreamTokenizer;
     35import java.io.StringReader;
     36import java.util.ArrayList;
     37import java.util.Collections;
     38import java.util.HashMap;
     39import java.util.Iterator;
     40import java.util.List;
     41import java.util.ListIterator;
     42import java.util.Map;
     43import javax.swing.UIManager;
     44
     45
     46/**
     47 * The MultiSplitLayout layout manager recursively arranges its
     48 * components in row and column groups called "Splits".  Elements of
     49 * the layout are separated by gaps called "Dividers".  The overall
     50 * layout is defined with a simple tree model whose nodes are
     51 * instances of MultiSplitLayout.Split, MultiSplitLayout.Divider,
     52 * and MultiSplitLayout.Leaf. Named Leaf nodes represent the space
     53 * allocated to a component that was added with a constraint that
     54 * matches the Leaf's name.  Extra space is distributed
     55 * among row/column siblings according to their 0.0 to 1.0 weight.
     56 * If no weights are specified then the last sibling always gets
     57 * all of the extra space, or space reduction.
     58 *
     59 * <p>
     60 * Although MultiSplitLayout can be used with any Container, it's
     61 * the default layout manager for MultiSplitPane.  MultiSplitPane
     62 * supports interactively dragging the Dividers, accessibility,
     63 * and other features associated with split panes.
     64 *
     65 * <p>
     66 * All properties in this class are bound: when a properties value
     67 * is changed, all PropertyChangeListeners are fired.
     68 *
     69 * @author Hans Muller
     70 * @see MultiSplitPane
     71 */
     72
     73public class MultiSplitLayout implements LayoutManager {
     74    private final Map<String, Component> childMap = new HashMap<String, Component>();
     75    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
     76    private Node model;
     77    private int dividerSize;
     78    private boolean floatingDividers = true;
     79
     80    /**
     81     * Create a MultiSplitLayout with a default model with a single
     82     * Leaf node named "default". 
     83     *
     84     * #see setModel
     85     */
     86    public MultiSplitLayout() {
     87        this(new Leaf("default"));
     88    }
     89   
     90    /**
     91     * Create a MultiSplitLayout with the specified model.
     92     *
     93     * #see setModel
     94     */
     95    public MultiSplitLayout(Node model) {
     96        this.model = model;
     97        this.dividerSize = UIManager.getInt("SplitPane.dividerSize");
     98        if (this.dividerSize == 0) {
     99            this.dividerSize = 7;
     100        }
     101    }
     102
     103    public void addPropertyChangeListener(PropertyChangeListener listener) {
     104        if (listener != null) {
     105            pcs.addPropertyChangeListener(listener);
     106        }
     107    }
     108    public void removePropertyChangeListener(PropertyChangeListener listener) {
     109        if (listener != null) {
     110            pcs.removePropertyChangeListener(listener);
     111        }
     112    }
     113    public PropertyChangeListener[] getPropertyChangeListeners() {
     114        return pcs.getPropertyChangeListeners();
     115    }
     116
     117    private void firePCS(String propertyName, Object oldValue, Object newValue) {
     118        if (!(oldValue != null && newValue != null && oldValue.equals(newValue))) {
     119            pcs.firePropertyChange(propertyName, oldValue, newValue);
     120        }
     121    }
     122
     123    /**
     124     * Return the root of the tree of Split, Leaf, and Divider nodes
     125     * that define this layout. 
     126     *
     127     * @return the value of the model property
     128     * @see #setModel
     129     */
     130    public Node getModel() { return model; }
     131
     132    /**
     133     * Set the root of the tree of Split, Leaf, and Divider nodes
     134     * that define this layout.  The model can be a Split node
     135     * (the typical case) or a Leaf.  The default value of this
     136     * property is a Leaf named "default".
     137     *
     138     * @param model the root of the tree of Split, Leaf, and Divider node
     139     * @throws IllegalArgumentException if model is a Divider or null
     140     * @see #getModel
     141     */
     142    public void setModel(Node model) {
     143        if ((model == null) || (model instanceof Divider)) {
     144            throw new IllegalArgumentException("invalid model");
     145        }
     146        Node oldModel = model;
     147        this.model = model;
     148        firePCS("model", oldModel, model);
     149    }
     150
     151    /**
     152     * Returns the width of Dividers in Split rows, and the height of
     153     * Dividers in Split columns.
     154     *
     155     * @return the value of the dividerSize property
     156     * @see #setDividerSize
     157     */
     158    public int getDividerSize() { return dividerSize; }
     159
     160    /**
     161     * Sets the width of Dividers in Split rows, and the height of
     162     * Dividers in Split columns.  The default value of this property
     163     * is the same as for JSplitPane Dividers.
     164     *
     165     * @param dividerSize the size of dividers (pixels)
     166     * @throws IllegalArgumentException if dividerSize < 0
     167     * @see #getDividerSize
     168     */
     169    public void setDividerSize(int dividerSize) {
     170        if (dividerSize < 0) {
     171            throw new IllegalArgumentException("invalid dividerSize");
     172        }
     173        int oldDividerSize = this.dividerSize;
     174        this.dividerSize = dividerSize;
     175        firePCS("dividerSize", oldDividerSize, dividerSize);
     176    }
     177
     178    /**
     179     * @return the value of the floatingDividers property
     180     * @see #setFloatingDividers
     181     */
     182    public boolean getFloatingDividers() { return floatingDividers; }
     183
     184
     185    /**
     186     * If true, Leaf node bounds match the corresponding component's
     187     * preferred size and Splits/Dividers are resized accordingly. 
     188     * If false then the Dividers define the bounds of the adjacent
     189     * Split and Leaf nodes.  Typically this property is set to false
     190     * after the (MultiSplitPane) user has dragged a Divider.
     191     *
     192     * @see #getFloatingDividers
     193     */
     194    public void setFloatingDividers(boolean floatingDividers) {
     195        boolean oldFloatingDividers = this.floatingDividers;
     196        this.floatingDividers = floatingDividers;
     197        firePCS("floatingDividers", oldFloatingDividers, floatingDividers);
     198    }
     199
     200
     201    /**
     202     * Add a component to this MultiSplitLayout.  The
     203     * <code>name</code> should match the name property of the Leaf
     204     * node that represents the bounds of <code>child</code>.  After
     205     * layoutContainer() recomputes the bounds of all of the nodes in
     206     * the model, it will set this child's bounds to the bounds of the
     207     * Leaf node with <code>name</code>.  Note: if a component was already
     208     * added with the same name, this method does not remove it from
     209     * its parent. 
     210     *
     211     * @param name identifies the Leaf node that defines the child's bounds
     212     * @param child the component to be added
     213     * @see #removeLayoutComponent
     214     */
     215    public void addLayoutComponent(String name, Component child) {
     216        if (name == null) {
     217            throw new IllegalArgumentException("name not specified");
     218        }
     219        childMap.put(name, child);
     220    }
     221
     222    /**
     223     * Removes the specified component from the layout.
     224     *
     225     * @param child the component to be removed
     226     * @see #addLayoutComponent
     227     */
     228    public void removeLayoutComponent(Component child) {
     229        String name = child.getName();
     230        if (name != null) {
     231            childMap.remove(name);
     232        }
     233    }
     234
     235    private Component childForNode(Node node) {
     236        if (node instanceof Leaf) {
     237            Leaf leaf = (Leaf)node;
     238            String name = leaf.getName();
     239            return (name != null) ? childMap.get(name) : null;
     240        }
     241        return null;
     242    }
     243
     244
     245    private Dimension preferredComponentSize(Node node) {
     246        Component child = childForNode(node);
     247        return (child != null) ? child.getPreferredSize() : new Dimension(0, 0);
     248       
     249    }
     250
     251    private Dimension minimumComponentSize(Node node) {
     252        Component child = childForNode(node);
     253        return (child != null) ? child.getMinimumSize() : new Dimension(0, 0);
     254       
     255    }
     256
     257    private Dimension preferredNodeSize(Node root) {
     258        if (root instanceof Leaf) {
     259            return preferredComponentSize(root);
     260        }
     261        else if (root instanceof Divider) {
     262            int dividerSize = getDividerSize();
     263            return new Dimension(dividerSize, dividerSize);
     264        }
     265        else {
     266            Split split = (Split)root;
     267            List<Node> splitChildren = split.getChildren();
     268            int width = 0;
     269            int height = 0;
     270            if (split.isRowLayout()) {
     271                for(Node splitChild : splitChildren) {
     272                    Dimension size = preferredNodeSize(splitChild);
     273                    width += size.width;
     274                    height = Math.max(height, size.height);
     275                }
     276            }
     277            else {
     278                for(Node splitChild : splitChildren) {
     279                    Dimension size = preferredNodeSize(splitChild);
     280                    width = Math.max(width, size.width);
     281                    height += size.height;
     282                }
     283            }
     284            return new Dimension(width, height);
     285        }
     286    }
     287
     288    private Dimension minimumNodeSize(Node root) {
     289        if (root instanceof Leaf) {
     290            Component child = childForNode(root);
     291            return (child != null) ? child.getMinimumSize() : new Dimension(0, 0);
     292        }
     293        else if (root instanceof Divider) {
     294            int dividerSize = getDividerSize();
     295            return new Dimension(dividerSize, dividerSize);
     296        }
     297        else {
     298            Split split = (Split)root;
     299            List<Node> splitChildren = split.getChildren();
     300            int width = 0;
     301            int height = 0;
     302            if (split.isRowLayout()) {
     303                for(Node splitChild : splitChildren) {
     304                    Dimension size = minimumNodeSize(splitChild);
     305                    width += size.width;
     306                    height = Math.max(height, size.height);
     307                }
     308            }
     309            else {
     310                for(Node splitChild : splitChildren) {
     311                    Dimension size = minimumNodeSize(splitChild);
     312                    width = Math.max(width, size.width);
     313                    height += size.height;
     314                }
     315            }
     316            return new Dimension(width, height);
     317        }
     318    }
     319
     320    private Dimension sizeWithInsets(Container parent, Dimension size) {
     321        Insets insets = parent.getInsets();
     322        int width = size.width + insets.left + insets.right;
     323        int height = size.height + insets.top + insets.bottom;
     324        return new Dimension(width, height);
     325    }
     326
     327    public Dimension preferredLayoutSize(Container parent) {
     328        Dimension size = preferredNodeSize(getModel());
     329        return sizeWithInsets(parent, size);
     330    }
     331
     332    public Dimension minimumLayoutSize(Container parent) {
     333        Dimension size = minimumNodeSize(getModel());
     334        return sizeWithInsets(parent, size);
     335    }
     336
     337
     338    private Rectangle boundsWithYandHeight(Rectangle bounds, double y, double height) {
     339        Rectangle r = new Rectangle();
     340        r.setBounds((int)(bounds.getX()), (int)y, (int)(bounds.getWidth()), (int)height);
     341        return r;
     342    }
     343
     344    private Rectangle boundsWithXandWidth(Rectangle bounds, double x, double width) {
     345        Rectangle r = new Rectangle();
     346        r.setBounds((int)x, (int)(bounds.getY()), (int)width, (int)(bounds.getHeight()));
     347        return r;
     348    }
     349
     350
     351    private void minimizeSplitBounds(Split split, Rectangle bounds) {
     352        Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0);
     353        List<Node> splitChildren = split.getChildren();
     354        Node lastChild = splitChildren.get(splitChildren.size() - 1);
     355        Rectangle lastChildBounds = lastChild.getBounds();
     356        if (split.isRowLayout()) {
     357            int lastChildMaxX = lastChildBounds.x + lastChildBounds.width;
     358            splitBounds.add(lastChildMaxX, bounds.y + bounds.height);
     359        }
     360        else {
     361            int lastChildMaxY = lastChildBounds.y + lastChildBounds.height;
     362            splitBounds.add(bounds.x + bounds.width, lastChildMaxY);
     363        }
     364        split.setBounds(splitBounds);
     365    }
     366
     367
     368    private void layoutShrink(Split split, Rectangle bounds) {
     369        Rectangle splitBounds = split.getBounds();
     370        ListIterator<Node> splitChildren = split.getChildren().listIterator();
     371        Node lastWeightedChild = split.lastWeightedChild();
     372
     373        if (split.isRowLayout()) {
     374            int totalWidth = 0;          // sum of the children's widths
     375            int minWeightedWidth = 0;    // sum of the weighted childrens' min widths
     376            int totalWeightedWidth = 0;  // sum of the weighted childrens' widths
     377            for(Node splitChild : split.getChildren()) {
     378                int nodeWidth = splitChild.getBounds().width;
     379                int nodeMinWidth = Math.min(nodeWidth, minimumNodeSize(splitChild).width);
     380                totalWidth += nodeWidth;
     381                if (splitChild.getWeight() > 0.0) {
     382                    minWeightedWidth += nodeMinWidth;
     383                    totalWeightedWidth += nodeWidth;
     384                }
     385            }
     386
     387            double x = bounds.getX();
     388            double extraWidth = splitBounds.getWidth() - bounds.getWidth();
     389            double availableWidth = extraWidth;
     390            boolean onlyShrinkWeightedComponents =
     391                (totalWeightedWidth - minWeightedWidth) > extraWidth;
     392
     393            while(splitChildren.hasNext()) {
     394                Node splitChild = splitChildren.next();
     395                Rectangle splitChildBounds = splitChild.getBounds();
     396                double minSplitChildWidth = minimumNodeSize(splitChild).getWidth();
     397                double splitChildWeight = (onlyShrinkWeightedComponents)
     398                    ? splitChild.getWeight()
     399                    : (splitChildBounds.getWidth() / (double)totalWidth);
     400
     401                if (!splitChildren.hasNext()) {
     402                    double newWidth =  Math.max(minSplitChildWidth, bounds.getMaxX() - x);
     403                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
     404                    layout2(splitChild, newSplitChildBounds);
     405                }
     406                else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) {
     407                    double allocatedWidth = Math.rint(splitChildWeight * extraWidth);
     408                    double oldWidth = splitChildBounds.getWidth();
     409                    double newWidth = Math.max(minSplitChildWidth, oldWidth - allocatedWidth);
     410                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
     411                    layout2(splitChild, newSplitChildBounds);
     412                    availableWidth -= (oldWidth - splitChild.getBounds().getWidth());
     413                }
     414                else {
     415                    double existingWidth = splitChildBounds.getWidth();
     416                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth);
     417                    layout2(splitChild, newSplitChildBounds);
     418                }
     419                x = splitChild.getBounds().getMaxX();
     420            }
     421        }
     422
     423        else {
     424            int totalHeight = 0;          // sum of the children's heights
     425            int minWeightedHeight = 0;    // sum of the weighted childrens' min heights
     426            int totalWeightedHeight = 0;  // sum of the weighted childrens' heights
     427            for(Node splitChild : split.getChildren()) {
     428                int nodeHeight = splitChild.getBounds().height;
     429                int nodeMinHeight = Math.min(nodeHeight, minimumNodeSize(splitChild).height);
     430                totalHeight += nodeHeight;
     431                if (splitChild.getWeight() > 0.0) {
     432                    minWeightedHeight += nodeMinHeight;
     433                    totalWeightedHeight += nodeHeight;
     434                }
     435            }
     436
     437            double y = bounds.getY();
     438            double extraHeight = splitBounds.getHeight() - bounds.getHeight();
     439            double availableHeight = extraHeight;
     440            boolean onlyShrinkWeightedComponents =
     441                (totalWeightedHeight - minWeightedHeight) > extraHeight;
     442
     443            while(splitChildren.hasNext()) {
     444                Node splitChild = splitChildren.next();
     445                Rectangle splitChildBounds = splitChild.getBounds();
     446                double minSplitChildHeight = minimumNodeSize(splitChild).getHeight();
     447                double splitChildWeight = (onlyShrinkWeightedComponents)
     448                    ? splitChild.getWeight()
     449                    : (splitChildBounds.getHeight() / (double)totalHeight);
     450
     451                if (!splitChildren.hasNext()) {
     452                    double oldHeight = splitChildBounds.getHeight();
     453                    double newHeight =  Math.max(minSplitChildHeight, bounds.getMaxY() - y);
     454                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
     455                    layout2(splitChild, newSplitChildBounds);
     456                    availableHeight -= (oldHeight - splitChild.getBounds().getHeight());
     457                }
     458                else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
     459                    double allocatedHeight = Math.rint(splitChildWeight * extraHeight);
     460                    double oldHeight = splitChildBounds.getHeight();
     461                    double newHeight = Math.max(minSplitChildHeight, oldHeight - allocatedHeight);
     462                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
     463                    layout2(splitChild, newSplitChildBounds);
     464                    availableHeight -= (oldHeight - splitChild.getBounds().getHeight());
     465                }
     466                else {
     467                    double existingHeight = splitChildBounds.getHeight();
     468                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight);
     469                    layout2(splitChild, newSplitChildBounds);
     470                }
     471                y = splitChild.getBounds().getMaxY();
     472            }
     473        }
     474
     475        /* The bounds of the Split node root are set to be
     476         * big enough to contain all of its children. Since
     477         * Leaf children can't be reduced below their
     478         * (corresponding java.awt.Component) minimum sizes,
     479         * the size of the Split's bounds maybe be larger than
     480         * the bounds we were asked to fit within.
     481         */
     482        minimizeSplitBounds(split, bounds);
     483    }
     484
     485
     486    private void layoutGrow(Split split, Rectangle bounds) {
     487        Rectangle splitBounds = split.getBounds();
     488        ListIterator<Node> splitChildren = split.getChildren().listIterator();
     489        Node lastWeightedChild = split.lastWeightedChild();
     490
     491        /* Layout the Split's child Nodes' along the X axis.  The bounds
     492         * of each child will have the same y coordinate and height as the
     493         * layoutGrow() bounds argument.  Extra width is allocated to the
     494         * to each child with a non-zero weight:
     495         *     newWidth = currentWidth + (extraWidth * splitChild.getWeight())
     496         * Any extraWidth "left over" (that's availableWidth in the loop
     497         * below) is given to the last child.  Note that Dividers always
     498         * have a weight of zero, and they're never the last child.
     499         */
     500        if (split.isRowLayout()) {
     501            double x = bounds.getX();
     502            double extraWidth = bounds.getWidth() - splitBounds.getWidth();
     503            double availableWidth = extraWidth;
     504
     505            while(splitChildren.hasNext()) {
     506                Node splitChild = splitChildren.next();
     507                Rectangle splitChildBounds = splitChild.getBounds();
     508                double splitChildWeight = splitChild.getWeight();
     509
     510                if (!splitChildren.hasNext()) { 
     511                    double newWidth = bounds.getMaxX() - x;
     512                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
     513                    layout2(splitChild, newSplitChildBounds);
     514                }
     515                else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) {
     516                    double allocatedWidth = (splitChild.equals(lastWeightedChild))
     517                        ? availableWidth
     518                        : Math.rint(splitChildWeight * extraWidth);
     519                    double newWidth = splitChildBounds.getWidth() + allocatedWidth;
     520                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth);
     521                    layout2(splitChild, newSplitChildBounds);
     522                    availableWidth -= allocatedWidth;
     523                }
     524                else {
     525                    double existingWidth = splitChildBounds.getWidth();
     526                    Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth);
     527                    layout2(splitChild, newSplitChildBounds);
     528                }
     529                x = splitChild.getBounds().getMaxX();
     530            }
     531        }
     532
     533        /* Layout the Split's child Nodes' along the Y axis.  The bounds
     534         * of each child will have the same x coordinate and width as the
     535         * layoutGrow() bounds argument.  Extra height is allocated to the
     536         * to each child with a non-zero weight:
     537         *     newHeight = currentHeight + (extraHeight * splitChild.getWeight())
     538         * Any extraHeight "left over" (that's availableHeight in the loop
     539         * below) is given to the last child.  Note that Dividers always
     540         * have a weight of zero, and they're never the last child.
     541         */
     542        else {
     543            double y = bounds.getY();
     544            double extraHeight = bounds.getMaxY() - splitBounds.getHeight();
     545            double availableHeight = extraHeight;
     546
     547            while(splitChildren.hasNext()) {
     548                Node splitChild = splitChildren.next();
     549                Rectangle splitChildBounds = splitChild.getBounds();
     550                double splitChildWeight = splitChild.getWeight();
     551               
     552                if (!splitChildren.hasNext()) {
     553                    double newHeight = bounds.getMaxY() - y;
     554                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
     555                    layout2(splitChild, newSplitChildBounds);
     556                }
     557                else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) {
     558                    double allocatedHeight = (splitChild.equals(lastWeightedChild))
     559                        ? availableHeight
     560                        : Math.rint(splitChildWeight * extraHeight);
     561                    double newHeight = splitChildBounds.getHeight() + allocatedHeight;
     562                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight);
     563                    layout2(splitChild, newSplitChildBounds);
     564                    availableHeight -= allocatedHeight;
     565                }
     566                else {
     567                    double existingHeight = splitChildBounds.getHeight();
     568                    Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight);
     569                    layout2(splitChild, newSplitChildBounds);
     570                }
     571                y = splitChild.getBounds().getMaxY();
     572            }
     573        }
     574    }
     575
     576
     577    /* Second pass of the layout algorithm: branch to layoutGrow/Shrink
     578     * as needed.
     579     */
     580   private void layout2(Node root, Rectangle bounds) {
     581        if (root instanceof Leaf) {
     582            Component child = childForNode(root);
     583            if (child != null) {
     584                child.setBounds(bounds);
     585            }
     586            root.setBounds(bounds);
     587        }
     588        else if (root instanceof Divider) {
     589            root.setBounds(bounds);
     590        }
     591        else if (root instanceof Split) {
     592            Split split = (Split)root;
     593            boolean grow = split.isRowLayout()
     594                ? (split.getBounds().width <= bounds.width)
     595                : (split.getBounds().height <= bounds.height);
     596            if (grow) {
     597                layoutGrow(split, bounds);
     598                root.setBounds(bounds);
     599            }
     600            else {
     601                layoutShrink(split, bounds);
     602                // split.setBounds() called in layoutShrink()
     603            }
     604        }
     605    }
     606
     607
     608    /* First pass of the layout algorithm.
     609     *
     610     * If the Dividers are "floating" then set the bounds of each
     611     * node to accomodate the preferred size of all of the
     612     * Leaf's java.awt.Components.  Otherwise, just set the bounds
     613     * of each Leaf/Split node so that it's to the left of (for
     614     * Split.isRowLayout() Split children) or directly above
     615     * the Divider that follows.
     616     *
     617     * This pass sets the bounds of each Node in the layout model.  It
     618     * does not resize any of the parent Container's
     619     * (java.awt.Component) children.  That's done in the second pass,
     620     * see layoutGrow() and layoutShrink().
     621     */
     622    private void layout1(Node root, Rectangle bounds) {
     623        if (root instanceof Leaf) {
     624            root.setBounds(bounds);
     625        }
     626        else if (root instanceof Split) {
     627            Split split = (Split)root;
     628            Iterator<Node> splitChildren = split.getChildren().iterator();
     629            Rectangle childBounds = null;
     630            int dividerSize = getDividerSize();
     631           
     632            /* Layout the Split's child Nodes' along the X axis.  The bounds
     633             * of each child will have the same y coordinate and height as the
     634             * layout1() bounds argument. 
     635             *
     636             * Note: the column layout code - that's the "else" clause below
     637             * this if, is identical to the X axis (rowLayout) code below.
     638             */
     639            if (split.isRowLayout()) {
     640                double x = bounds.getX();
     641                while(splitChildren.hasNext()) {
     642                    Node splitChild = splitChildren.next();
     643                    Divider dividerChild =
     644                        (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
     645
     646                    double childWidth = 0.0;
     647                    if (getFloatingDividers()) {
     648                        childWidth = preferredNodeSize(splitChild).getWidth();
     649                    }
     650                    else {
     651                        if (dividerChild != null) {
     652                            childWidth = dividerChild.getBounds().getX() - x;
     653                        }
     654                        else {
     655                            childWidth = split.getBounds().getMaxX() - x;
     656                        }
     657                    }
     658                    childBounds = boundsWithXandWidth(bounds, x, childWidth);
     659                    layout1(splitChild, childBounds);
     660
     661                    if (getFloatingDividers() && (dividerChild != null)) {
     662                        double dividerX = childBounds.getMaxX();
     663                        Rectangle dividerBounds = boundsWithXandWidth(bounds, dividerX, dividerSize);
     664                        dividerChild.setBounds(dividerBounds);
     665                    }
     666                    if (dividerChild != null) {
     667                        x = dividerChild.getBounds().getMaxX();
     668                    }
     669                }
     670            }
     671
     672            /* Layout the Split's child Nodes' along the Y axis.  The bounds
     673             * of each child will have the same x coordinate and width as the
     674             * layout1() bounds argument.  The algorithm is identical to what's
     675             * explained above, for the X axis case.
     676             */
     677            else {
     678                double y = bounds.getY();
     679                while(splitChildren.hasNext()) {
     680                    Node splitChild = splitChildren.next();
     681                    Divider dividerChild =
     682                        (splitChildren.hasNext()) ? (Divider)(splitChildren.next()) : null;
     683
     684                    double childHeight = 0.0;
     685                    if (getFloatingDividers()) {
     686                        childHeight = preferredNodeSize(splitChild).getHeight();
     687                    }
     688                    else {
     689                        if (dividerChild != null) {
     690                            childHeight = dividerChild.getBounds().getY() - y;
     691                        }
     692                        else {
     693                            childHeight = split.getBounds().getMaxY() - y;
     694                        }
     695                    }
     696                    childBounds = boundsWithYandHeight(bounds, y, childHeight);
     697                    layout1(splitChild, childBounds);
     698
     699                    if (getFloatingDividers() && (dividerChild != null)) {
     700                        double dividerY = childBounds.getMaxY();
     701                        Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, dividerSize);
     702                        dividerChild.setBounds(dividerBounds);
     703                    }
     704                    if (dividerChild != null) {
     705                        y = dividerChild.getBounds().getMaxY();
     706                    }
     707                }
     708            }
     709            /* The bounds of the Split node root are set to be just
     710             * big enough to contain all of its children, but only
     711             * along the axis it's allocating space on.  That's
     712             * X for rows, Y for columns.  The second pass of the
     713             * layout algorithm - see layoutShrink()/layoutGrow()
     714             * allocates extra space.
     715             */
     716            minimizeSplitBounds(split, bounds);
     717        }
     718    }
     719
     720    /**
     721     * The specified Node is either the wrong type or was configured
     722     * incorrectly.
     723     */
     724    public static class InvalidLayoutException extends RuntimeException {
     725        private final Node node;
     726        public InvalidLayoutException (String msg, Node node) {
     727            super(msg);
     728            this.node = node;
     729        }
     730        /**
     731         * @return the invalid Node.
     732         */
     733        public Node getNode() { return node; }
     734    }
     735
     736    private void throwInvalidLayout(String msg, Node node) {
     737        throw new InvalidLayoutException(msg, node);
     738    }
     739
     740    private void checkLayout(Node root) {
     741        if (root instanceof Split) {
     742            Split split = (Split)root;
     743            if (split.getChildren().size() <= 2) {
     744                throwInvalidLayout("Split must have > 2 children", root);
     745            }
     746            Iterator<Node> splitChildren = split.getChildren().iterator();
     747            double weight = 0.0;
     748            while(splitChildren.hasNext()) {
     749                Node splitChild = splitChildren.next();
     750                if (splitChild instanceof Divider) {
     751                    throwInvalidLayout("expected a Split or Leaf Node", splitChild);
     752                }
     753                if (splitChildren.hasNext()) {
     754                    Node dividerChild = splitChildren.next();
     755                    if (!(dividerChild instanceof Divider)) {
     756                        throwInvalidLayout("expected a Divider Node", dividerChild);
     757                    }
     758                }
     759                weight += splitChild.getWeight();
     760                checkLayout(splitChild);
     761            }
     762            if (weight > 1.0) {
     763                throwInvalidLayout("Split children's total weight > 1.0", root);
     764            }
     765        }
     766    }
     767
     768    /**
     769     * Compute the bounds of all of the Split/Divider/Leaf Nodes in
     770     * the layout model, and then set the bounds of each child component
     771     * with a matching Leaf Node.
     772     */
     773    public void layoutContainer(Container parent) {
     774        checkLayout(getModel());
     775        Insets insets = parent.getInsets();
     776        Dimension size = parent.getSize();
     777        int width = size.width - (insets.left + insets.right);
     778        int height = size.height - (insets.top + insets.bottom);
     779        Rectangle bounds = new Rectangle(insets.left, insets.top, width, height);
     780        layout1(getModel(), bounds);
     781        layout2(getModel(), bounds);
     782    }
     783
     784
     785    private Divider dividerAt(Node root, int x, int y) {
     786        if (root instanceof Divider) {
     787            Divider divider = (Divider)root;
     788            return (divider.getBounds().contains(x, y)) ? divider : null;
     789        }
     790        else if (root instanceof Split) {
     791            Split split = (Split)root;
     792            for(Node child : split.getChildren()) {
     793                if (child.getBounds().contains(x, y)) {
     794                    return dividerAt(child, x, y);
     795                }
     796            }
     797        }
     798        return null;
     799    }
     800
     801    /**
     802     * Return the Divider whose bounds contain the specified
     803     * point, or null if there isn't one.
     804     *
     805     * @param x x coordinate
     806     * @param y y coordinate
     807     * @return the Divider at x,y
     808     */
     809    public Divider dividerAt(int x, int y) {
     810        return dividerAt(getModel(), x, y);
     811    }
     812
     813    private boolean nodeOverlapsRectangle(Node node, Rectangle r2) {
     814        Rectangle r1 = node.getBounds();
     815        return
     816            (r1.x <= (r2.x + r2.width)) && ((r1.x + r1.width) >= r2.x) &&
     817            (r1.y <= (r2.y + r2.height)) && ((r1.y + r1.height) >= r2.y);
     818    }
     819
     820    private List<Divider> dividersThatOverlap(Node root, Rectangle r) {
     821        if (nodeOverlapsRectangle(root, r) && (root instanceof Split)) {
     822            List<Divider> dividers = new ArrayList<Divider>();
     823            for(Node child : ((Split)root).getChildren()) {
     824                if (child instanceof Divider) {
     825                    if (nodeOverlapsRectangle(child, r)) {
     826                        dividers.add((Divider)child);
     827                    }
     828                }
     829                else if (child instanceof Split) {
     830                    dividers.addAll(dividersThatOverlap(child, r));
     831                }
     832            }
     833            return dividers;
     834        }
     835        else {
     836            return Collections.emptyList();
     837        }
     838    }
     839
     840    /**
     841     * Return the Dividers whose bounds overlap the specified
     842     * Rectangle.
     843     *
     844     * @param r target Rectangle
     845     * @return the Dividers that overlap r
     846     * @throws IllegalArgumentException if the Rectangle is null
     847     */
     848    public List<Divider> dividersThatOverlap(Rectangle r) {
     849        if (r == null) {
     850            throw new IllegalArgumentException("null Rectangle");
     851        }
     852        return dividersThatOverlap(getModel(), r);
     853    }
     854
     855
     856    /**
     857     * Base class for the nodes that model a MultiSplitLayout.
     858     */
     859    public static abstract class Node {
     860        private Split parent = null; 
     861        private Rectangle bounds = new Rectangle();
     862        private double weight = 0.0;
     863
     864        /**
     865         * Returns the Split parent of this Node, or null.
     866         *
     867         * @return the value of the parent property.
     868         * @see #setParent
     869         */
     870        public Split getParent() { return parent; }
     871
     872        /**
     873         * Set the value of this Node's parent property.  The default
     874         * value of this property is null.
     875         *
     876         * @param parent a Split or null
     877         * @see #getParent
     878         */
     879        public void setParent(Split parent) {
     880            this.parent = parent;
     881        }
     882       
     883        /**
     884         * Returns the bounding Rectangle for this Node.
     885         *
     886         * @return the value of the bounds property.
     887         * @see #setBounds
     888         */
     889        public Rectangle getBounds() {
     890            return new Rectangle(this.bounds);
     891        }
     892
     893        /**
     894         * Set the bounding Rectangle for this node.  The value of
     895         * bounds may not be null.  The default value of bounds
     896         * is equal to <code>new Rectangle(0,0,0,0)</code>.
     897         *
     898         * @param bounds the new value of the bounds property
     899         * @throws IllegalArgumentException if bounds is null
     900         * @see #getBounds
     901         */
     902        public void setBounds(Rectangle bounds) {
     903            if (bounds == null) {
     904                throw new IllegalArgumentException("null bounds");
     905            }
     906            this.bounds = new Rectangle(bounds);
     907        }
     908
     909        /**
     910         * Value between 0.0 and 1.0 used to compute how much space
     911         * to add to this sibling when the layout grows or how
     912         * much to reduce when the layout shrinks.
     913         *
     914         * @return the value of the weight property
     915         * @see #setWeight
     916         */
     917        public double getWeight() { return weight; }
     918
     919        /**
     920         * The weight property is a between 0.0 and 1.0 used to
     921         * compute how much space to add to this sibling when the
     922         * layout grows or how much to reduce when the layout shrinks.
     923         * If rowLayout is true then this node's width grows
     924         * or shrinks by (extraSpace * weight).  If rowLayout is false,
     925         * then the node's height is changed.  The default value
     926         * of weight is 0.0.
     927         *
     928         * @param weight a double between 0.0 and 1.0
     929         * @see #getWeight
     930         * @see MultiSplitLayout#layoutContainer
     931         * @throws IllegalArgumentException if weight is not between 0.0 and 1.0
     932         */
     933        public void setWeight(double weight) {
     934            if ((weight < 0.0)|| (weight > 1.0)) {
     935                throw new IllegalArgumentException("invalid weight");
     936            }
     937            this.weight = weight;
     938        }
     939
     940        private Node siblingAtOffset(int offset) {
     941            Split parent = getParent();
     942            if (parent == null) { return null; }
     943            List<Node> siblings = parent.getChildren();
     944            int index = siblings.indexOf(this);
     945            if (index == -1) { return null; }
     946            index += offset;
     947            return ((index > -1) && (index < siblings.size())) ? siblings.get(index) : null;
     948        }
     949           
     950        /**
     951         * Return the Node that comes after this one in the parent's
     952         * list of children, or null.  If this node's parent is null,
     953         * or if it's the last child, then return null.
     954         *
     955         * @return the Node that comes after this one in the parent's list of children.
     956         * @see #previousSibling
     957         * @see #getParent
     958         */
     959        public Node nextSibling() {
     960            return siblingAtOffset(+1);
     961        }
     962
     963        /**
     964         * Return the Node that comes before this one in the parent's
     965         * list of children, or null.  If this node's parent is null,
     966         * or if it's the last child, then return null.
     967         *
     968         * @return the Node that comes before this one in the parent's list of children.
     969         * @see #nextSibling
     970         * @see #getParent
     971         */
     972        public Node previousSibling() {
     973            return siblingAtOffset(-1);
     974        }
     975    }
     976
     977    /**
     978     * Defines a vertical or horizontal subdivision into two or more
     979     * tiles.
     980     */
     981    public static class Split extends Node {
     982        private List<Node> children = Collections.emptyList();
     983        private boolean rowLayout = true;
     984
     985        /**
     986         * Returns true if the this Split's children are to be
     987         * laid out in a row: all the same height, left edge
     988         * equal to the previous Node's right edge.  If false,
     989         * children are laid on in a column.
     990         *
     991         * @return the value of the rowLayout property.
     992         * @see #setRowLayout
     993         */
     994        public boolean isRowLayout() { return rowLayout; }
     995
     996        /**
     997         * Set the rowLayout property.  If true, all of this Split's
     998         * children are to be laid out in a row: all the same height,
     999         * each node's left edge equal to the previous Node's right
     1000         * edge.  If false, children are laid on in a column.  Default
     1001         * value is true.
     1002         *
     1003         * @param rowLayout true for horizontal row layout, false for column
     1004         * @see #isRowLayout
     1005         */
     1006        public void setRowLayout(boolean rowLayout) {
     1007            this.rowLayout = rowLayout;
     1008        }
     1009
     1010        /**
     1011         * Returns this Split node's children.  The returned value
     1012         * is not a reference to the Split's internal list of children
     1013         *
     1014         * @return the value of the children property.
     1015         * @see #setChildren
     1016         */
     1017        public List<Node> getChildren() {
     1018            return new ArrayList<Node>(children);
     1019        }
     1020
     1021        /**
     1022         * Set's the children property of this Split node.  The parent
     1023         * of each new child is set to this Split node, and the parent
     1024         * of each old child (if any) is set to null.  This method
     1025         * defensively copies the incoming List.  Default value is
     1026         * an empty List.
     1027         *
     1028         * @param children List of children
     1029         * @see #getChildren
     1030         * @throws IllegalArgumentException if children is null
     1031         */
     1032        public void setChildren(List<Node> children) {
     1033            if (children == null) {
     1034                throw new IllegalArgumentException("children must be a non-null List");
     1035            }
     1036            for(Node child : this.children) {
     1037                child.setParent(null);
     1038            }
     1039            this.children = new ArrayList<Node>(children);
     1040            for(Node child : this.children) {
     1041                child.setParent(this);
     1042            }
     1043        }
     1044
     1045        /**
     1046         * Convenience method that returns the last child whose weight
     1047         * is > 0.0.
     1048         *
     1049         * @return the last child whose weight is > 0.0.
     1050         * @see #getChildren
     1051         * @see Node#getWeight
     1052         */
     1053        public final Node lastWeightedChild() {
     1054            List<Node> children = getChildren();
     1055            Node weightedChild = null;
     1056            for(Node child : children) {
     1057                if (child.getWeight() > 0.0) {
     1058                    weightedChild = child;
     1059                }
     1060            }
     1061            return weightedChild;
     1062        }
     1063
     1064        public String toString() {
     1065            int nChildren = getChildren().size();
     1066            StringBuffer sb = new StringBuffer("MultiSplitLayout.Split");
     1067            sb.append(isRowLayout() ? " ROW [" : " COLUMN [");
     1068            sb.append(nChildren + ((nChildren == 1) ? " child" : " children"));
     1069            sb.append("] ");
     1070            sb.append(getBounds());
     1071            return sb.toString();
     1072        }
     1073    }
     1074
     1075
     1076    /**
     1077     * Models a java.awt Component child.
     1078     */
     1079    public static class Leaf extends Node {
     1080        private String name = "";
     1081
     1082        /**
     1083         * Create a Leaf node.  The default value of name is "".
     1084         */
     1085        public Leaf() { }
     1086
     1087        /**
     1088         * Create a Leaf node with the specified name.  Name can not
     1089         * be null.
     1090         *
     1091         * @param name value of the Leaf's name property
     1092         * @throws IllegalArgumentException if name is null
     1093         */
     1094        public Leaf(String name) {
     1095            if (name == null) {
     1096                throw new IllegalArgumentException("name is null");
     1097            }
     1098            this.name = name;
     1099        }
     1100
     1101        /**
     1102         * Return the Leaf's name.
     1103         *
     1104         * @return the value of the name property.
     1105         * @see #setName
     1106         */
     1107        public String getName() { return name; }
     1108
     1109        /**
     1110         * Set the value of the name property.  Name may not be null.
     1111         *
     1112         * @param name value of the name property
     1113         * @throws IllegalArgumentException if name is null
     1114         */
     1115        public void setName(String name) {
     1116            if (name == null) {
     1117                throw new IllegalArgumentException("name is null");
     1118            }
     1119            this.name = name;
     1120        }
     1121
     1122        public String toString() {
     1123            StringBuffer sb = new StringBuffer("MultiSplitLayout.Leaf");
     1124            sb.append(" \"");
     1125            sb.append(getName());
     1126            sb.append("\"");
     1127            sb.append(" weight=");
     1128            sb.append(getWeight());
     1129            sb.append(" ");
     1130            sb.append(getBounds());
     1131            return sb.toString();
     1132        }
     1133    }
     1134
     1135
     1136    /**
     1137     * Models a single vertical/horiztonal divider.
     1138     */
     1139    public static class Divider extends Node {
     1140        /**
     1141         * Convenience method, returns true if the Divider's parent
     1142         * is a Split row (a Split with isRowLayout() true), false
     1143         * otherwise. In other words if this Divider's major axis
     1144         * is vertical, return true.
     1145         *
     1146         * @return true if this Divider is part of a Split row.
     1147         */
     1148        public final boolean isVertical() {
     1149            Split parent = getParent();
     1150            return (parent != null) ? parent.isRowLayout() : false;
     1151        }
     1152
     1153        /**
     1154         * Dividers can't have a weight, they don't grow or shrink.
     1155         * @throws UnsupportedOperationException
     1156         */
     1157        public void setWeight(double weight) {
     1158            throw new UnsupportedOperationException();
     1159        }
     1160
     1161        public String toString() {
     1162            return "MultiSplitLayout.Divider " + getBounds().toString();
     1163        }
     1164    }
     1165
     1166
     1167    private static void throwParseException(StreamTokenizer st, String msg) throws Exception {
     1168        throw new Exception("MultiSplitLayout.parseModel Error: " + msg);
     1169    }
     1170
     1171    private static void parseAttribute(String name, StreamTokenizer st, Node node) throws Exception {
     1172        if ((st.nextToken() != '=')) {
     1173            throwParseException(st, "expected '=' after " + name);
     1174        }
     1175        if (name.equalsIgnoreCase("WEIGHT")) {
     1176            if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
     1177                node.setWeight(st.nval);
     1178            }
     1179            else {
     1180                throwParseException(st, "invalid weight");
     1181            }
     1182        }
     1183        else if (name.equalsIgnoreCase("NAME")) {
     1184            if (st.nextToken() == StreamTokenizer.TT_WORD) {
     1185                if (node instanceof Leaf) {
     1186                    ((Leaf)node).setName(st.sval);
     1187                }
     1188                else {
     1189                    throwParseException(st, "can't specify name for " + node);
     1190                }
     1191            }
     1192            else {
     1193                throwParseException(st, "invalid name");
     1194            }
     1195        }
     1196        else {
     1197            throwParseException(st, "unrecognized attribute \"" + name + "\"");
     1198        }
     1199    }
     1200
     1201    private static void addSplitChild(Split parent, Node child) {
     1202        List<Node> children = new ArrayList<Node>(parent.getChildren());
     1203        if (children.size() == 0) {
     1204            children.add(child);
     1205        }
     1206        else {
     1207            children.add(new Divider());
     1208            children.add(child);
     1209        }
     1210        parent.setChildren(children);
     1211    }
     1212
     1213    private static void parseLeaf(StreamTokenizer st, Split parent) throws Exception {
     1214        Leaf leaf = new Leaf();
     1215        int token;
     1216        while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
     1217            if (token == ')') {
     1218                break;
     1219            }
     1220            if (token == StreamTokenizer.TT_WORD) {
     1221                parseAttribute(st.sval, st, leaf);
     1222            }
     1223            else {
     1224                throwParseException(st, "Bad Leaf: " + leaf);
     1225            }
     1226        }
     1227        addSplitChild(parent, leaf);
     1228    }
     1229
     1230    private static void parseSplit(StreamTokenizer st, Split parent) throws Exception {
     1231        int token;
     1232        while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
     1233            if (token == ')') {
     1234                break;
     1235            }
     1236            else if (token == StreamTokenizer.TT_WORD) {
     1237                if (st.sval.equalsIgnoreCase("WEIGHT")) {
     1238                    parseAttribute(st.sval, st, parent);
     1239                }
     1240                else {
     1241                    addSplitChild(parent, new Leaf(st.sval));
     1242                }
     1243            }
     1244            else if (token == '(') {
     1245                if ((token = st.nextToken()) != StreamTokenizer.TT_WORD) {
     1246                    throwParseException(st, "invalid node type");
     1247                }
     1248                String nodeType = st.sval.toUpperCase();
     1249                if (nodeType.equals("LEAF")) {
     1250                    parseLeaf(st, parent);
     1251                }
     1252                else if (nodeType.equals("ROW") || nodeType.equals("COLUMN")) {
     1253                    Split split = new Split();
     1254                    split.setRowLayout(nodeType.equals("ROW"));
     1255                    addSplitChild(parent, split);
     1256                    parseSplit(st, split);
     1257                }
     1258                else {
     1259                    throwParseException(st, "unrecognized node type '" + nodeType + "'");
     1260                }
     1261            }
     1262        }
     1263    }
     1264
     1265    private static Node parseModel (Reader r) {
     1266        StreamTokenizer st = new StreamTokenizer(r);
     1267        try {
     1268            Split root = new Split();
     1269            parseSplit(st, root);
     1270            return root.getChildren().get(0);
     1271        }
     1272        catch (Exception e) {
     1273            System.err.println(e);
     1274        }
     1275        finally {
     1276            try { r.close(); } catch (IOException ignore) {}
     1277        }
     1278        return null;
     1279    }
     1280
     1281    /**
     1282     * A convenience method that converts a string to a
     1283     * MultiSplitLayout model (a tree of Nodes) using a
     1284     * a simple syntax.  Nodes are represented by
     1285     * parenthetical expressions whose first token
     1286     * is one of ROW/COLUMN/LEAF.  ROW and COLUMN specify
     1287     * horizontal and vertical Split nodes respectively,
     1288     * LEAF specifies a Leaf node.  A Leaf's name and
     1289     * weight can be specified with attributes,
     1290     * name=<i>myLeafName</i> weight=<i>myLeafWeight</i>.
     1291     * Similarly, a Split's weight can be specified with
     1292     * weight=<i>mySplitWeight</i>.
     1293     *
     1294     * <p> For example, the following expression generates
     1295     * a horizontal Split node with three children:
     1296     * the Leafs named left and right, and a Divider in
     1297     * between:
     1298     * <pre>
     1299     * (ROW (LEAF name=left) (LEAF name=right weight=1.0))
     1300     * </pre>
     1301     *
     1302     * <p> Dividers should not be included in the string,
     1303     * they're added automatcially as needed.  Because
     1304     * Leaf nodes often only need to specify a name, one
     1305     * can specify a Leaf by just providing the name.
     1306     * The previous example can be written like this:
     1307     * <pre>
     1308     * (ROW left (LEAF name=right weight=1.0))
     1309     * </pre>
     1310     *
     1311     * <p>Here's a more complex example.  One row with
     1312     * three elements, the first and last of which are columns
     1313     * with two leaves each:
     1314     * <pre>
     1315     * (ROW (COLUMN weight=0.5 left.top left.bottom)
     1316     *      (LEAF name=middle)
     1317     *      (COLUMN weight=0.5 right.top right.bottom))
     1318     * </pre>
     1319     *
     1320     *
     1321     * <p> This syntax is not intended for archiving or
     1322     * configuration files .  It's just a convenience for
     1323     * examples and tests.
     1324     *
     1325     * @return the Node root of a tree based on s.
     1326     */
     1327    public static Node parseModel(String s) {
     1328        return parseModel(new StringReader(s));
     1329    }
     1330
     1331
     1332    private static void printModel(String indent, Node root) {
     1333        if (root instanceof Split) {
     1334            Split split = (Split)root;
     1335            System.out.println(indent + split);
     1336            for(Node child : split.getChildren()) {
     1337                printModel(indent + "  ", child);
     1338            }
     1339        }
     1340        else {
     1341            System.out.println(indent + root);
     1342        }
     1343    }
     1344
     1345    /**
     1346     * Print the tree with enough detail for simple debugging.
     1347     */
     1348    public static void printModel(Node root) {
     1349        printModel("", root);
     1350    }
     1351}
  • src/org/jdesktop/swingx/MultiSplitPane.java

     
     1/*
     2 * $Id: MultiSplitPane.java,v 1.15 2005/10/26 14:29:54 hansmuller Exp $
     3 *
     4 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
     5 * Santa Clara, California 95054, U.S.A. All rights reserved.
     6 *
     7 * This library is free software; you can redistribute it and/or
     8 * modify it under the terms of the GNU Lesser General Public
     9 * License as published by the Free Software Foundation; either
     10 * version 2.1 of the License, or (at your option) any later version.
     11 *
     12 * This library is distributed in the hope that it will be useful,
     13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15 * Lesser General Public License for more details.
     16 *
     17 * You should have received a copy of the GNU Lesser General Public
     18 * License along with this library; if not, write to the Free Software
     19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 */
     21
     22package org.jdesktop.swingx;
     23
     24import java.awt.Color;
     25import java.awt.Cursor;
     26import java.awt.Graphics;
     27import java.awt.Graphics2D;
     28import java.awt.Rectangle;
     29import java.awt.event.KeyEvent;
     30import java.awt.event.KeyListener;
     31import java.awt.event.MouseEvent;
     32import javax.accessibility.AccessibleContext;
     33import javax.accessibility.AccessibleRole;
     34import javax.swing.JPanel;
     35import javax.swing.event.MouseInputAdapter;
     36import org.jdesktop.swingx.MultiSplitLayout.Divider;
     37import org.jdesktop.swingx.MultiSplitLayout.Node;
     38
     39/**
     40 *
     41 * <p>
     42 * All properties in this class are bound: when a properties value
     43 * is changed, all PropertyChangeListeners are fired.
     44 *
     45 * @author Hans Muller
     46 */
     47public class MultiSplitPane extends JPanel {
     48    private AccessibleContext accessibleContext = null;
     49    private boolean continuousLayout = true;
     50    private DividerPainter dividerPainter = new DefaultDividerPainter();
     51
     52    /**
     53     * Creates a MultiSplitPane with it's LayoutManager set to
     54     * to an empty MultiSplitLayout.
     55     */
     56    public MultiSplitPane() {
     57        super(new MultiSplitLayout());
     58        InputHandler inputHandler = new InputHandler();
     59        addMouseListener(inputHandler);
     60        addMouseMotionListener(inputHandler);
     61        addKeyListener(inputHandler);
     62        setFocusable(true);
     63    }
     64
     65    /**
     66     * A convenience method that returns the layout manager cast
     67     * to MutliSplitLayout.
     68     *
     69     * @return this MultiSplitPane's layout manager
     70     * @see java.awt.Container#getLayout
     71     * @see #setModel
     72     */
     73    public final MultiSplitLayout getMultiSplitLayout() {
     74        return (MultiSplitLayout)getLayout();
     75    }
     76
     77    /**
     78     * A convenience method that sets the MultiSplitLayout model.
     79     * Equivalent to <code>getMultiSplitLayout.setModel(model)</code>
     80     *
     81     * @param model the root of the MultiSplitLayout model
     82     * @see #getMultiSplitLayout
     83     * @see MultiSplitLayout#setModel
     84     */
     85    public final void setModel(Node model) {
     86        getMultiSplitLayout().setModel(model);
     87    }
     88
     89    /**
     90     * A convenience method that sets the MultiSplitLayout dividerSize
     91     * property. Equivalent to
     92     * <code>getMultiSplitLayout().setDividerSize(newDividerSize)</code>.
     93     *
     94     * @param dividerSize the value of the dividerSize property
     95     * @see #getMultiSplitLayout
     96     * @see MultiSplitLayout#setDividerSize
     97     */
     98    public final void setDividerSize(int dividerSize) {
     99        getMultiSplitLayout().setDividerSize(dividerSize);
     100    }
     101
     102    /**
     103     * Sets the value of the <code>continuousLayout</code> property.
     104     * If true, then the layout is revalidated continuously while
     105     * a divider is being moved.  The default value of this property
     106     * is true.
     107     *
     108     * @param continuousLayout value of the continuousLayout property
     109     * @see #isContinuousLayout
     110     */
     111    public void setContinuousLayout(boolean continuousLayout) {
     112        boolean oldContinuousLayout = continuousLayout;
     113        this.continuousLayout = continuousLayout;
     114        firePropertyChange("continuousLayout", oldContinuousLayout, continuousLayout);
     115    }
     116
     117    /**
     118     * Returns true if dragging a divider only updates
     119     * the layout when the drag gesture ends (typically, when the
     120     * mouse button is released).
     121     *
     122     * @return the value of the <code>continuousLayout</code> property
     123     * @see #setContinuousLayout
     124     */
     125    public boolean isContinuousLayout() {
     126        return continuousLayout;
     127    }
     128
     129    /**
     130     * Returns the Divider that's currently being moved, typically
     131     * because the user is dragging it, or null.
     132     *
     133     * @return the Divider that's being moved or null.
     134     */
     135    public Divider activeDivider() {
     136        return dragDivider;
     137    }
     138
     139    /**
     140     * Draws a single Divider.  Typically used to specialize the
     141     * way the active Divider is painted. 
     142     *
     143     * @see #getDividerPainter
     144     * @see #setDividerPainter
     145     */
     146    public static abstract class DividerPainter {
     147        /**
     148         * Paint a single Divider.       
     149         *
     150         * @param g the Graphics object to paint with
     151         * @param divider the Divider to paint
     152         */
     153        public abstract void paint(Graphics g, Divider divider);
     154    }
     155
     156    private class DefaultDividerPainter extends DividerPainter {
     157        public void paint(Graphics g, Divider divider) {
     158            if ((divider == activeDivider()) && !isContinuousLayout()) {
     159                Graphics2D g2d = (Graphics2D)g;
     160                g2d.setColor(Color.black);
     161                g2d.fill(divider.getBounds());
     162            }
     163        }
     164    }
     165
     166    /**
     167     * The DividerPainter that's used to paint Dividers on this MultiSplitPane.
     168     * This property may be null.
     169     *
     170     * @return the value of the dividerPainter Property
     171     * @see #setDividerPainter
     172     */
     173    public DividerPainter getDividerPainter() {
     174        return dividerPainter;
     175    }
     176
     177    /**
     178     * Sets the DividerPainter that's used to paint Dividers on this
     179     * MultiSplitPane.  The default DividerPainter only draws
     180     * the activeDivider (if there is one) and then, only if
     181     * continuousLayout is false.  The value of this property is
     182     * used by the paintChildren method: Dividers are painted after
     183     * the MultiSplitPane's children have been rendered so that
     184     * the activeDivider can appear "on top of" the children.
     185     *
     186     * @param dividerPainter the value of the dividerPainter property, can be null
     187     * @see #paintChildren
     188     * @see #activeDivider
     189     */
     190    public void setDividerPainter(DividerPainter dividerPainter) {
     191        this.dividerPainter = dividerPainter;
     192    }
     193
     194    /**
     195     * Uses the DividerPainter (if any) to paint each Divider that
     196     * overlaps the clip Rectangle.  This is done after the call to
     197     * <code>super.paintChildren()</code> so that Dividers can be
     198     * rendered "on top of" the children.
     199     * <p>
     200     * {@inheritDoc}
     201     */
     202    protected void paintChildren(Graphics g) {
     203        super.paintChildren(g);
     204        DividerPainter dp = getDividerPainter();
     205        Rectangle clipR = g.getClipBounds();
     206        if ((dp != null) && (clipR != null)) {
     207            Graphics dpg = g.create();
     208            try {
     209                MultiSplitLayout msl = getMultiSplitLayout();
     210                for(Divider divider : msl.dividersThatOverlap(clipR)) {
     211                    dp.paint(dpg, divider);
     212                }
     213            }
     214            finally {
     215                dpg.dispose();
     216            }
     217        }
     218    }
     219
     220    private boolean dragUnderway = false;
     221    private MultiSplitLayout.Divider dragDivider = null;
     222    private Rectangle initialDividerBounds = null;
     223    private boolean oldFloatingDividers = true;
     224    private int dragOffsetX = 0;
     225    private int dragOffsetY = 0;
     226    private int dragMin = -1;
     227    private int dragMax = -1;
     228   
     229    private void startDrag(int mx, int my) {
     230        requestFocusInWindow();
     231        MultiSplitLayout msl = getMultiSplitLayout();
     232        MultiSplitLayout.Divider divider = msl.dividerAt(mx, my);
     233        if (divider != null) {
     234            MultiSplitLayout.Node prevNode = divider.previousSibling();
     235            MultiSplitLayout.Node nextNode = divider.nextSibling();
     236            if ((prevNode == null) || (nextNode == null)) {
     237                dragUnderway = false;
     238            }
     239            else {
     240                initialDividerBounds = divider.getBounds();
     241                dragOffsetX = mx - initialDividerBounds.x;
     242                dragOffsetY = my - initialDividerBounds.y;
     243                dragDivider  = divider;
     244                Rectangle prevNodeBounds = prevNode.getBounds();
     245                Rectangle nextNodeBounds = nextNode.getBounds();
     246                if (dragDivider.isVertical()) {
     247                    dragMin = prevNodeBounds.x;
     248                    dragMax = nextNodeBounds.x + nextNodeBounds.width;
     249                    dragMax -= dragDivider.getBounds().width;
     250                }
     251                else {
     252                    dragMin = prevNodeBounds.y;
     253                    dragMax = nextNodeBounds.y + nextNodeBounds.height;
     254                    dragMax -= dragDivider.getBounds().height;
     255                }
     256                oldFloatingDividers = getMultiSplitLayout().getFloatingDividers();
     257                getMultiSplitLayout().setFloatingDividers(false);
     258                dragUnderway = true;
     259            }
     260        }
     261        else {
     262            dragUnderway = false;
     263        }
     264    }
     265
     266    private void repaintDragLimits() {
     267        Rectangle damageR = dragDivider.getBounds();
     268        if (dragDivider.isVertical()) {
     269            damageR.x = dragMin;
     270            damageR.width = dragMax - dragMin;
     271        }
     272        else {
     273            damageR.y = dragMin;
     274            damageR.height = dragMax - dragMin;
     275        }
     276        repaint(damageR);
     277    }
     278
     279    private void updateDrag(int mx, int my) {
     280        if (!dragUnderway) {
     281            return;
     282        }
     283        Rectangle oldBounds = dragDivider.getBounds();
     284        Rectangle bounds = new Rectangle(oldBounds);
     285        if (dragDivider.isVertical()) {
     286            bounds.x = mx - dragOffsetX;
     287            bounds.x = Math.max(bounds.x, dragMin);
     288            bounds.x = Math.min(bounds.x, dragMax);
     289        }
     290        else {
     291            bounds.y = my - dragOffsetY;
     292            bounds.y = Math.max(bounds.y, dragMin);
     293            bounds.y = Math.min(bounds.y, dragMax);
     294        }
     295        dragDivider.setBounds(bounds);
     296        if (isContinuousLayout()) {
     297            revalidate();
     298            repaintDragLimits();
     299        }
     300        else {
     301            repaint(oldBounds.union(bounds));
     302        }
     303    }
     304
     305    private void clearDragState() {
     306        dragDivider = null;
     307        initialDividerBounds = null;
     308        oldFloatingDividers = true;
     309        dragOffsetX = dragOffsetY = 0;
     310        dragMin = dragMax = -1;
     311        dragUnderway = false;
     312    }
     313
     314    private void finishDrag(int x, int y) {
     315        if (dragUnderway) {
     316            clearDragState();
     317            if (!isContinuousLayout()) {
     318                revalidate();
     319                repaint();
     320            }
     321        }
     322    }
     323   
     324    private void cancelDrag() {       
     325        if (dragUnderway) {
     326            dragDivider.setBounds(initialDividerBounds);
     327            getMultiSplitLayout().setFloatingDividers(oldFloatingDividers);
     328            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
     329            repaint();
     330            revalidate();
     331            clearDragState();
     332        }
     333    }
     334
     335    private void updateCursor(int x, int y, boolean show) {
     336        if (dragUnderway) {
     337            return;
     338        }
     339        int cursorID = Cursor.DEFAULT_CURSOR;
     340        if (show) {
     341            MultiSplitLayout.Divider divider = getMultiSplitLayout().dividerAt(x, y);
     342            if (divider != null) {
     343                cursorID  = (divider.isVertical()) ?
     344                    Cursor.E_RESIZE_CURSOR :
     345                    Cursor.N_RESIZE_CURSOR;
     346            }
     347        }
     348        setCursor(Cursor.getPredefinedCursor(cursorID));
     349    }
     350
     351
     352    private class InputHandler extends MouseInputAdapter implements KeyListener {
     353
     354        public void mouseEntered(MouseEvent e) {
     355            updateCursor(e.getX(), e.getY(), true);
     356        }
     357
     358        public void mouseMoved(MouseEvent e) {
     359            updateCursor(e.getX(), e.getY(), true);
     360        }
     361
     362        public void mouseExited(MouseEvent e) {
     363            updateCursor(e.getX(), e.getY(), false);
     364        }
     365
     366        public void mousePressed(MouseEvent e) {
     367            startDrag(e.getX(), e.getY());
     368        }
     369        public void mouseReleased(MouseEvent e) {
     370            finishDrag(e.getX(), e.getY());
     371        }
     372        public void mouseDragged(MouseEvent e) {
     373            updateDrag(e.getX(), e.getY());         
     374        }
     375        public void keyPressed(KeyEvent e) {
     376            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
     377                cancelDrag();
     378            }
     379        }
     380        public void keyReleased(KeyEvent e) { }
     381        public void keyTyped(KeyEvent e) { }
     382    }
     383
     384    public AccessibleContext getAccessibleContext() {
     385        if( accessibleContext == null ) {
     386            accessibleContext = new AccessibleMultiSplitPane();
     387        }
     388        return accessibleContext;
     389    }
     390   
     391    protected class AccessibleMultiSplitPane extends AccessibleJPanel {
     392        public AccessibleRole getAccessibleRole() {
     393            return AccessibleRole.SPLIT_PANE;
     394        }
     395    }
     396}
  • src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java

     
    8484
    8585    public SelectionListDialog() {
    8686        super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
    87                 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
     87                Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
    8888
    8989        selectionHistory = new LinkedList<Collection<? extends OsmPrimitive>>();
    9090        popupMenu = new JPopupMenu();
  • src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java

     
    3333import org.openstreetmap.josm.Main;
    3434import org.openstreetmap.josm.actions.JosmAction;
    3535import org.openstreetmap.josm.actions.HelpAction.Helpful;
     36import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
    3637import org.openstreetmap.josm.tools.GBC;
    3738import org.openstreetmap.josm.tools.ImageProvider;
    3839import org.openstreetmap.josm.tools.Shortcut;
     
    4344 *
    4445 */
    4546public class ToggleDialog extends JPanel implements Helpful {
    46 //    private static final Logger logger = Logger.getLogger(ToggleDialog.class.getName());
    47 
    48     /**
    49      * The action to toggle the visibility state of this toggle dialog.
    50      *
    51      * Emits {@see PropertyChangeEvent}s for the property <tt>selected</tt>:
    52      * <ul>
    53      *   <li>true, if the dialog is currently visible</li>
    54      *   <li>false, if the dialog is currently invisible</li>
    55      * </ul>
    56      *
    57      */
    58     public final class ToggleDialogAction extends JosmAction {
    59         private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut, String prefname) {
    60             super(name, iconName, tooltip, shortcut, false);
    61         }
    62 
    63         public void actionPerformed(ActionEvent e) {
    64             toggleVisibility();
    65         }
    66 
    67         public void toggleVisibility() {
    68             if (isShowing) {
    69                 hideDialog();
    70             } else {
    71                 showDialog();
    72             }
    73         }
    74     }
    75 
    76     /**
    77      * The action to toggle this dialog.
    78      */
     47    /** The action to toggle this dialog */
    7948    private ToggleDialogAction toggleAction;
    8049    private String preferencePrefix;
    8150
    82     private JPanel parent;
     51    /** DialogsPanel that manages all ToggleDialogs */
     52    private DialogsPanel dialogsPanel;
     53   
    8354    private  TitleBar titleBar;
    8455    private String title;
    8556
     
    10071
    10172    /** the preferred height if the toggle dialog is expanded */
    10273    private int preferredHeight;
     74   
    10375    /** the label in the title bar which shows whether the toggle dialog is expanded or collapsed */
    10476    private JLabel lblMinimized;
     77   
    10578    /** the JDialog displaying the toggle dialog as undocked dialog */
    10679    private JDialog detachedDialog;
    10780
    10881    /**
     82     * Constructor
     83     * (see below)
     84     */
     85    public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
     86        this(name, iconName, tooltip, shortcut, preferredHeight, false);
     87    }
     88    /**
    10989     * Constructor
    11090     *
    11191     * @param name  the name of the dialog
     
    11393     * @param tooltip  the tool tip
    11494     * @param shortcut  the shortcut
    11595     * @param preferredHeight the preferred height for the dialog
     96     * @param defShow if the dialog should be shown by default, if there is no preference
    11697     */
    117     public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
     98    public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow) {
    11899        super(new BorderLayout());
    119100        this.preferencePrefix = iconName;
    120         init(name, iconName, tooltip, shortcut, preferredHeight);
    121     }
    122101
    123     /**
    124      * Initializes the toggle dialog
    125      *
    126      * @param name
    127      * @param iconName
    128      * @param tooltip
    129      * @param shortcut
    130      * @param preferredHeight
    131      */
    132     private void init(String name, String iconName, String tooltip, Shortcut shortcut, final int preferredHeight) {
    133102        /** Use the full width of the parent element */
    134103        setPreferredSize(new Dimension(0, preferredHeight));
    135104        /** Override any minimum sizes of child elements so the user can resize freely */
     
    146115        titleBar = new TitleBar(name, iconName);
    147116        add(titleBar, BorderLayout.NORTH);
    148117
    149         setVisible(false);
    150118        setBorder(BorderFactory.createEtchedBorder());
    151 
     119       
     120        isShowing = Main.pref.getBoolean(preferencePrefix+".visible", defShow);
    152121        isDocked = Main.pref.getBoolean(preferencePrefix+".docked", true);
    153122        isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
    154123    }
    155124
    156125    /**
    157      * Sets the visibility of all components in this toggle dialog, except the title bar
     126     * The action to toggle the visibility state of this toggle dialog.
    158127     *
    159      * @param visible true, if the components should be visible; false otherwise
     128     * Emits {@see PropertyChangeEvent}s for the property <tt>selected</tt>:
     129     * <ul>
     130     *   <li>true, if the dialog is currently visible</li>
     131     *   <li>false, if the dialog is currently invisible</li>
     132     * </ul>
     133     *
    160134     */
    161     protected void setContentVisible(boolean visible) {
    162         Component comps[] = getComponents();
    163         for(int i=0; i<comps.length; i++) {
    164             if(comps[i] != titleBar) {
    165                 comps[i].setVisible(visible);
     135    public final class ToggleDialogAction extends JosmAction {
     136        private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut, String prefname) {
     137            super(name, iconName, tooltip, shortcut, false);
     138        }
     139
     140        public void actionPerformed(ActionEvent e) {
     141            if (isShowing) {
     142                hideDialog();
     143                dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
     144            } else {
     145                showDialog();
     146                expand();
     147                dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
    166148            }
    167149        }
    168150    }
    169151
    170152    /**
    171      * Toggles between collapsed and expanded state
    172      *
     153     * Shows the dialog
    173154     */
    174     protected void toggleExpandedState() {
    175         if (isCollapsed) {
    176             expand();
     155    public void showDialog() {
     156        setIsShowing(true);
     157        if (!isDocked) {
     158            detach();
    177159        } else {
    178             collapse();
     160            dock();
     161            this.setVisible(true);
    179162        }
     163        // toggling the selected value in order to enforce PropertyChangeEvents
     164        setIsShowing(true);
     165        toggleAction.putValue("selected", false);
     166        toggleAction.putValue("selected", true);
    180167    }
    181 
     168   
    182169    /**
    183      * Collapses the toggle dialog to the title bar only
    184      *
     170     * Hides the dialog
    185171     */
    186     protected void collapse() {
    187         setContentVisible(false);
    188         isCollapsed = true;
    189         Main.pref.put(preferencePrefix+".minimized", true);
    190         setPreferredSize(new Dimension(0,20));
    191         setMaximumSize(new Dimension(Integer.MAX_VALUE,20));
    192         lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
    193         refreshToggleDialogsView();
     172    public void hideDialog() {
     173        closeDetachedDialog();
     174        this.setVisible(false);
     175        setIsShowing(false);
     176        toggleAction.putValue("selected", false);
    194177    }
    195178
    196179    /**
    197      * Expands the toggle dialog
    198      */
    199     protected void expand() {
    200         setContentVisible(true);
    201         isCollapsed = false;
    202         Main.pref.put(preferencePrefix+".minimized", false);
    203         setPreferredSize(new Dimension(0,preferredHeight));
    204         setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
    205         lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
    206         refreshToggleDialogsView();
    207     }
    208 
    209     /**
    210      * Replies the index of this toggle dialog in the view of
    211      * toggle dialog.
    212      *
    213      * @return
    214      */
    215     protected int getDialogPosition() {
    216         if (parent == null) return -1;
    217         for (int i=0; i< parent.getComponentCount(); i++) {
    218             String name = parent.getComponent(i).getName();
    219             if (name != null && name.equals(this.getName()))
    220                 return i;
    221         }
    222         return -1;
    223     }
    224 
    225     /**
    226180     * Displays the toggle dialog in the toggle dialog view on the right
    227181     * of the main map window.
    228182     *
    229183     */
    230184    protected void dock() {
    231185        detachedDialog = null;
    232         if (parent == null) return;
    233 
    234         // check whether the toggle dialog view contains a placeholder
    235         // for this toggle dialog. If so, replace it with this dialog.
    236         //
    237         int idx = getDialogPosition();
    238         if (idx > -1) {
    239             parent.remove(idx);
    240             if (idx >= parent.getComponentCount()) {
    241                 parent.add(ToggleDialog.this);
    242             } else {
    243                 parent.add(ToggleDialog.this,idx);
    244             }
    245         } else {
    246             parent.add(ToggleDialog.this);
    247         }
    248         parent.validate();
    249 
    250         if(Main.pref.getBoolean(preferencePrefix+".visible")) {
    251             setVisible(true);
    252         } else {
    253             setVisible(false);
    254         }
    255186        titleBar.setVisible(true);
    256         isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
    257         if (isCollapsed) {
    258             collapse();
    259         } else {
    260             expand();
    261         }
    262         isDocked = true;
    263         Main.pref.put(preferencePrefix+".docked", isDocked);
     187        setIsDocked(true);
    264188    }
    265189
    266190    /**
     
    269193     */
    270194    protected void detach() {
    271195        setContentVisible(true);
    272         setVisible(true);
    273         // replace the toggle dialog by an invisible place holder. Makes sure
    274         // we can place the toggle dialog where it was when it becomes docked
    275         // again.
    276         //
    277         if (parent != null) {
    278             int idx = getDialogPosition();
    279             if (idx > -1) {
    280                 JPanel placeHolder = new JPanel();
    281                 placeHolder.setName(this.getName());
    282                 placeHolder.setVisible(false);
    283                 parent.add(placeHolder,idx);
    284             }
    285             parent.remove(ToggleDialog.this);
    286         }
    287        
    288 
     196        this.setVisible(true);       
    289197        titleBar.setVisible(false);
    290198        detachedDialog = new DetachedDialog();
    291199        detachedDialog.setVisible(true);
    292         refreshToggleDialogsView();
    293         isDocked = false;
    294         Main.pref.put(preferencePrefix+".docked", isDocked);
     200        setIsDocked(false);
    295201    }
    296202
    297203    /**
    298      * Hides the dialog
     204     * Collapses the toggle dialog to the title bar only
     205     *
    299206     */
    300     public void hideDialog() {
    301         closeDetachedDialog();
    302         setVisible(false);
    303         isShowing = false;
    304         Main.pref.put(preferencePrefix+".visible", false);
    305         refreshToggleDialogsView();
    306         toggleAction.putValue("selected", false);
     207    public void collapse() {
     208        setContentVisible(false);
     209        setIsCollapsed(true);
     210        setPreferredSize(new Dimension(0,20));
     211        setMaximumSize(new Dimension(Integer.MAX_VALUE,20));
     212        setMinimumSize(new Dimension(Integer.MAX_VALUE,20));
     213        lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
    307214    }
    308215
    309216    /**
    310      * Replies true if this dialog is showing either as docked or as detached dialog
     217     * Expands the toggle dialog
    311218     */
    312     public boolean isDialogShowing() {
    313         return this.isShowing;
     219    protected void expand() {
     220        setContentVisible(true);
     221        setIsCollapsed(false);
     222        setPreferredSize(new Dimension(0,preferredHeight));
     223        setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
     224        lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
    314225    }
    315226
    316227    /**
    317      * Shows the dialog
     228     * Sets the visibility of all components in this toggle dialog, except the title bar
     229     *
     230     * @param visible true, if the components should be visible; false otherwise
    318231     */
    319     public void showDialog() {
    320         if (!isDocked) {
    321             detach();
    322         } else {
    323             dock();
    324             if (!isCollapsed) {
    325                 expand();
    326                 setVisible(true);
    327                 refreshToggleDialogsView();
    328             } else {
    329                 setVisible(true);
    330                 refreshToggleDialogsView();
     232    protected void setContentVisible(boolean visible) {
     233        Component comps[] = getComponents();
     234        for(int i=0; i<comps.length; i++) {
     235            if(comps[i] != titleBar) {
     236                comps[i].setVisible(visible);
    331237            }
    332238        }
    333         isShowing = true;
    334         // toggling the selected value in order to enforce PropertyChangeEvents
    335         toggleAction.putValue("selected", false);
    336         toggleAction.putValue("selected", true);
    337         Main.pref.put(preferencePrefix+".visible", true);
    338239    }
    339240
    340241    /**
    341      * Refreshes the layout of the parent toggle dialog view
    342      *
    343      */
    344     protected void refreshToggleDialogsView() {
    345         if(parent != null){
    346             parent.validate();
    347         }
    348     }
    349 
    350     /**
    351242     * Closes the the detached dialog if this toggle dialog is currently displayed
    352243     * in a detached dialog.
    353244     *
     
    360251        }
    361252    }
    362253
    363     public String helpTopic() {
    364         String help = getClass().getName();
    365         help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
    366         return "Dialog/"+help;
    367     }
    368254
    369255    /**
    370      * Replies the action to toggle the visible state of this toggle dialog
    371      *
    372      * @return the action to toggle the visible state of this toggle dialog
    373      */
    374     public AbstractAction getToggleAction() {
    375         return toggleAction;
    376     }
    377 
    378     /**
    379      * Replies the prefix for the preference settings of this dialog.
    380      *
    381      * @return the prefix for the preference settings of this dialog.
    382      */
    383     public String getPreferencePrefix() {
    384         return preferencePrefix;
    385     }
    386 
    387     /**
    388      * Sets the parent displaying all toggle dialogs
    389      *
    390      * @param parent the parent
    391      */
    392     public void setParent(JPanel parent) {
    393         this.parent = parent;
    394     }
    395 
    396     /**
    397      * Replies the name of this toggle dialog
    398      *
    399      */
    400     @Override
    401     public String getName() {
    402         return "toggleDialog." + preferencePrefix;
    403     }
    404 
    405     /**
    406      * Sets the title
    407      *
    408      * @param title the title
    409      */
    410     public void setTitle(String title) {
    411         titleBar.setTitle(title);
    412     }
    413 
    414     /**
    415256     * The title bar displayed in docked mode
    416257     *
    417258     */
     
    449290                    new MouseAdapter() {
    450291                        @Override
    451292                        public void mouseClicked(MouseEvent e) {
    452                             toggleExpandedState();
     293//                            toggleExpandedState();
     294                            if (isCollapsed) {
     295                                expand();
     296                                dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
     297                            } else {
     298                                collapse();
     299                                dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
     300                            }
    453301                        }
    454302                    }
    455303            );
     
    462310                    new ActionListener(){
    463311                        public void actionPerformed(ActionEvent e) {
    464312                            detach();
     313                            dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
    465314                        }
    466315                    }
    467316            );
     
    475324                    new ActionListener(){
    476325                        public void actionPerformed(ActionEvent e) {
    477326                            hideDialog();
     327                            dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
    478328                        }
    479329                    }
    480330            );
     
    506356                    getContentPane().removeAll();
    507357                    dispose();
    508358                    dock();
     359                    expand();
     360                    dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
    509361                }
    510362            });
    511363            String bounds = Main.pref.get(preferencePrefix+".bounds",null);
     
    541393    protected Dimension getDefaultDetachedSize() {
    542394        return new Dimension(Main.map.DEF_TOGGLE_DLG_WIDTH, preferredHeight);
    543395    }
     396
     397    /**
     398     * Replies the action to toggle the visible state of this toggle dialog
     399     *
     400     * @return the action to toggle the visible state of this toggle dialog
     401     */
     402    public AbstractAction getToggleAction() {
     403        return toggleAction;
     404    }
     405
     406    /**
     407     * Replies the prefix for the preference settings of this dialog.
     408     *
     409     * @return the prefix for the preference settings of this dialog.
     410     */
     411    public String getPreferencePrefix() {
     412        return preferencePrefix;
     413    }
     414
     415    /**
     416     * Sets the dialogsPanel managing all toggle dialogs
     417     */
     418    public void setDialogsPanel(DialogsPanel dialogsPanel) {
     419        this.dialogsPanel = dialogsPanel;
     420    }
     421
     422    /**
     423     * Replies the name of this toggle dialog
     424     */
     425    @Override
     426    public String getName() {
     427        return "toggleDialog." + preferencePrefix;
     428    }
     429
     430    /**
     431     * Sets the title
     432     */
     433    public void setTitle(String title) {
     434        titleBar.setTitle(title);
     435    }
     436   
     437    private void setIsShowing(boolean val) {
     438        isShowing = val;
     439        Main.pref.put(preferencePrefix+".visible", val);
     440    }
     441
     442    private void setIsDocked(boolean val) {
     443        isDocked = val;
     444        Main.pref.put(preferencePrefix+".docked", val);
     445    }
     446
     447    private void setIsCollapsed(boolean val) {
     448        isCollapsed = val;
     449        Main.pref.put(preferencePrefix+".minimized", val);
     450    }
     451
     452    public int getPreferredHeight() {
     453        return preferredHeight;
     454    }
     455
     456    /**
     457     * Replies true if this dialog is showing either as docked or as detached dialog
     458     */
     459    public boolean isDialogShowing() {
     460        return isShowing;
     461    }
     462
     463    /**
     464     * Replies true if this dialog is docked and expanded
     465     */
     466    public boolean isDialogInDefaultView() {
     467        return isShowing && isDocked && (! isCollapsed);
     468    }
     469
     470    /**
     471     * Replies true if this dialog is docked and collapsed
     472     */
     473    public boolean isDialogInCollapsedView() {
     474        return isShowing && isDocked && isCollapsed;
     475    }
     476
     477    public String helpTopic() {
     478        String help = getClass().getName();
     479        help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
     480        return "Dialog/"+help;
     481    }
    544482}
  • src/org/openstreetmap/josm/gui/dialogs/DialogsPanel.java

     
     1// License: GPL. See LICENSE file for details.
     2
     3package org.openstreetmap.josm.gui.dialogs;
     4
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6
     7import java.awt.Dimension;
     8
     9import java.util.ArrayList;
     10import java.util.List;
     11
     12import javax.swing.BoxLayout;
     13import javax.swing.JPanel;
     14
     15import org.jdesktop.swingx.MultiSplitLayout;
     16import org.jdesktop.swingx.MultiSplitLayout.Node;
     17import org.jdesktop.swingx.MultiSplitLayout.Leaf;
     18import org.jdesktop.swingx.MultiSplitLayout.Divider;
     19import org.jdesktop.swingx.MultiSplitLayout.Split;
     20import org.jdesktop.swingx.MultiSplitPane;
     21
     22import org.openstreetmap.josm.Main;
     23
     24public class DialogsPanel extends JPanel {
     25    protected List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
     26    protected MultiSplitPane mSpltPane = new MultiSplitPane();
     27    final protected int DIVIDER_SIZE = 5;
     28   
     29    /**
     30     * Panels that are added to the multisplitpane.
     31     */
     32    private List<JPanel> panels = new ArrayList<JPanel>();
     33
     34    private boolean initialized = false;   
     35    public void initialize(List<ToggleDialog> allDialogs) {
     36        if (initialized) {
     37            throw new IllegalStateException();
     38        }
     39        initialized = true;
     40        this.allDialogs = allDialogs;
     41       
     42        for (Integer i=0; i < allDialogs.size(); ++i) {
     43            final ToggleDialog dlg = allDialogs.get(i);
     44            dlg.setDialogsPanel(this);           
     45            dlg.setVisible(false);
     46        }
     47        for (int i=0; i < allDialogs.size() + 1; ++i) {
     48            final JPanel p = new JPanel() {
     49                /**
     50                 * Honoured by the MultiSplitPaneLayout when the
     51                 * entire Window is resized.
     52                 */
     53                public Dimension getMinimumSize() {
     54                    return new Dimension(0, 40);   
     55                }
     56            };
     57            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
     58            p.setVisible(false);
     59
     60            mSpltPane.add(p, "L"+i);
     61            panels.add(p);
     62        }
     63
     64        for (Integer i=0; i < allDialogs.size(); ++i) {
     65            final ToggleDialog dlg = allDialogs.get(i);
     66            if (dlg.isDialogShowing()) {
     67                dlg.showDialog();
     68                if (dlg.isDialogInCollapsedView()) {
     69                    dlg.collapse();
     70                }           
     71            } else {
     72                dlg.hideDialog();
     73            }
     74        }
     75        this.add(mSpltPane);
     76        reconstruct(Action.ELEMENT_SHRINKS, null);
     77    }
     78
     79    /**
     80     * What action was performed to trigger the reconstruction
     81     */
     82    public enum Action {
     83        INVISIBLE_TO_DEFAULT,
     84        COLLAPSED_TO_DEFAULT,
     85    /*  INVISIBLE_TO_COLLAPSED,    does not happen */
     86        ELEMENT_SHRINKS         /* else. (Remaining elements have more space.) */
     87    };
     88    /**
     89     * Reconstruct the view, if the configurations of dialogs has changed.
     90     * @param action what happened, so the reconstruction is necessary
     91     * @param triggeredBy the dialog that caused the reconstruction
     92     */
     93    public void reconstruct(Action action, ToggleDialog triggeredBy) {
     94
     95        final int N = allDialogs.size();
     96         
     97        /**
     98         * reset the panels
     99         */       
     100        for (int i=0; i < N; ++i) {
     101            final JPanel p = panels.get(i);
     102            p.removeAll();
     103            p.setVisible(false);
     104        }
     105
     106        /**
     107         * Add the elements to their respective panel.
     108         *
     109         * Each panel contains one dialog in default view and zero or more
     110         * collapsed dialogs on top of it. The last panel is an exception
     111         * as it can have collapsed dialogs at the bottom as well.
     112         * If there are no dialogs in default view, show the collapsed ones
     113         * in the last panel anyway.
     114         */
     115        int k = N-1;                // index of the current Panel (start with last one)
     116        JPanel p = panels.get(k);   // current Panel
     117        k = -1;                     // indicates that the current Panel index is N-1, but no default-view-Dialog was added to this Panel yet.
     118        for (int i=N-1; i >= 0 ; --i) {
     119            final ToggleDialog dlg = allDialogs.get(i);
     120            if (dlg.isDialogInDefaultView()) {
     121                if (k == -1) {
     122                    k = N-1;
     123                } else {
     124                    --k;
     125                    p = panels.get(k);
     126                }
     127                p.add(dlg, 0);
     128                p.setVisible(true);
     129            }
     130            else if (dlg.isDialogInCollapsedView()) {
     131                p.add(dlg, 0);
     132                p.setVisible(true);
     133            }   
     134        }
     135       
     136        if (k == -1) {
     137            k = N-1;
     138        }
     139        final int numPanels = N - k;
     140
     141        /**
     142         * Determine the panel geometry
     143         */
     144        if (action == Action.ELEMENT_SHRINKS) {
     145            for (int i=0; i<N; ++i) {
     146                final ToggleDialog dlg = allDialogs.get(i);
     147                if (dlg.isDialogInDefaultView()) {
     148                    final int ph = dlg.getPreferredHeight();
     149                    final int ah = dlg.getSize().height;
     150                    dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, (ah < 20 ? ph : ah)));
     151                }
     152            }
     153        } else {
     154            if (triggeredBy == null) {
     155                throw new IllegalArgumentException();
     156            }
     157           
     158            int sumP = 0;   // sum of preferred heights of dialogs in default view (without the triggering dialog)
     159            int sumA = 0;   // sum of actual heights of dialogs in default view (without the triggering dialog)
     160            int sumC = 0;   // sum of heights of all collapsed dialogs (triggering dialog is never collapsed)
     161
     162            for (int i=0; i<N; ++i) {
     163                final ToggleDialog dlg = allDialogs.get(i);
     164                if (dlg.isDialogInDefaultView()) {
     165                    if (dlg != triggeredBy) {
     166                        final int ph = dlg.getPreferredHeight();
     167                        final int ah = dlg.getSize().height;
     168                        sumP += ph;
     169                        sumA += ah;
     170                    }
     171                }
     172                else if (dlg.isDialogInCollapsedView()) {
     173                    sumC += dlg.getSize().height;
     174                }
     175            }
     176
     177            /** total Height */
     178            final int H = mSpltPane.getMultiSplitLayout().getModel().getBounds().getSize().height;
     179
     180            /** space, that is available for dialogs in default view (after the reconfiguration) */           
     181            final int s2 = H - (numPanels - 1) * DIVIDER_SIZE - sumC;
     182           
     183            final int hp_trig = triggeredBy.getPreferredHeight();
     184            if (hp_trig <= 0) throw new IllegalStateException(); // Must be positive
     185           
     186            /** The new dialog gets a fair share */
     187            final int hn_trig = hp_trig * s2 / (hp_trig + sumP);
     188            triggeredBy.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn_trig));
     189           
     190            /** This is remainig for the other default view dialogs */
     191            final int R = s2 - hn_trig;
     192           
     193            /**
     194             * Take space only from dialogs that are relatively large
     195             */
     196            int D_m = 0;        // additional space needed by the small dialogs
     197            int D_p = 0;        // available space from the large dialogs
     198            for (int i=0; i<N; ++i) {
     199                final ToggleDialog dlg = allDialogs.get(i);
     200                if (dlg.isDialogInDefaultView() && dlg != triggeredBy) {
     201                    final int ha = dlg.getSize().height;                              // current
     202                    final int h0 = ha * R / sumA;                                     // proportional shrinking
     203                    final int he = dlg.getPreferredHeight() * s2 / (sumP + hp_trig);  // fair share
     204                    if (h0 < he) {                  // dialog is relatively small
     205                        int hn = Math.min(ha, he);  // shrink less, but do not grow
     206                        D_m += hn - h0;
     207                    } else {                        // dialog is relatively large
     208                        D_p += h0 - he;
     209                    }
     210                }
     211            }
     212            /** adjust, without changing the sum */
     213            for (int i=0; i<N; ++i) {
     214                final ToggleDialog dlg = allDialogs.get(i);
     215                if (dlg.isDialogInDefaultView() && dlg != triggeredBy) {
     216                    final int ha = dlg.getSize().height;
     217                    final int h0 = ha * R / sumA;
     218                    final int he = dlg.getPreferredHeight() * s2 / (sumP + hp_trig);
     219                    if (h0 < he) {
     220                        int hn = Math.min(ha, he);
     221                        dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn));
     222                    } else {
     223                        int d;
     224                        try {
     225                            d = (h0-he) * D_m / D_p;
     226                        } catch (ArithmeticException e) { /* D_p may be zero - nothing wrong with that. */
     227                            d = 0;
     228                        };
     229                        dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, h0 - d));
     230                    }
     231                }
     232            }
     233        }
     234
     235        /**
     236         * create Layout
     237         */
     238        final List<Node> ch = new ArrayList<Node>();
     239       
     240        for (int i = k; i <= N-1; ++i) {
     241            if (i != k) {
     242                ch.add(new Divider());
     243            }
     244            Leaf l = new Leaf("L"+i);
     245            l.setWeight(1.0 / numPanels);
     246            ch.add(l);
     247        }
     248
     249        if (numPanels == 1) {
     250            Node model = ch.get(0);
     251            mSpltPane.getMultiSplitLayout().setModel(model);
     252        } else {
     253            Split model = new Split();
     254            model.setRowLayout(false);
     255            model.setChildren(ch);
     256            mSpltPane.getMultiSplitLayout().setModel(model);
     257        }
     258           
     259        mSpltPane.getMultiSplitLayout().setDividerSize(DIVIDER_SIZE);
     260        mSpltPane.getMultiSplitLayout().setFloatingDividers(true);
     261        mSpltPane.revalidate();       
     262    }
     263   
     264    public void destroy() {
     265        for (ToggleDialog t : allDialogs) {
     266            t.closeDetachedDialog();
     267        }
     268    }
     269
     270    /**
     271     * Replies the instance of a toggle dialog of type <code>type</code> managed by this
     272     * map frame
     273     *
     274     * @param <T>
     275     * @param type the class of the toggle dialog, i.e. UserListDialog.class
     276     * @return the instance of a toggle dialog of type <code>type</code> managed by this
     277     * map frame; null, if no such dialog exists
     278     *
     279     */
     280    public <T> T getToggleDialog(Class<T> type) {
     281        for (ToggleDialog td : allDialogs) {
     282            if (type.isInstance(td))
     283                return type.cast(td);
     284        }
     285        return null;
     286    }
     287}
  • src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java

     
    2828
    2929    public CommandStackDialog(final MapFrame mapFrame) {
    3030        super(tr("Command Stack"), "commandstack", tr("Open a list of all commands (undo buffer)."),
    31                 Shortcut.registerShortcut("subwindow:commandstack", tr("Toggle: {0}", tr("Command Stack")), KeyEvent.VK_O, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 100);
     31                Shortcut.registerShortcut("subwindow:commandstack", tr("Toggle: {0}", tr("Command Stack")), KeyEvent.VK_O, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 100, true);
    3232        Main.main.undoRedo.listenerCommands.add(this);
    3333
    3434        tree.setRootVisible(false);
  • src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java

     
    143143     */
    144144    protected LayerListDialog(MapFrame mapFrame) {
    145145        super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
    146                 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100);
     146                Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true);
    147147
    148148        // create the models
    149149        //
  • src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java

     
    453453    public PropertiesDialog(MapFrame mapFrame) {
    454454        super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
    455455                Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
    456                         Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
     456                        Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
    457457
    458458        // setting up the properties table
    459459        propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
  • src/org/openstreetmap/josm/gui/MapFrame.java

     
    77import java.awt.Container;
    88import java.awt.Dimension;
    99import java.util.ArrayList;
     10import java.util.List;
    1011
    1112import javax.swing.AbstractButton;
    1213import javax.swing.Action;
     
    2829import org.openstreetmap.josm.actions.mapmode.ZoomAction;
    2930import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
    3031import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
     32import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
    3133import org.openstreetmap.josm.gui.dialogs.FilterDialog;
    3234import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
    3335import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     
    7476     * The panel list of all toggle dialog icons. To add new toggle dialog actions, use addToggleDialog
    7577     * instead of adding directly to this list.
    7678     */
    77     private JPanel toggleDialogs = new JPanel();
    78     private ArrayList<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
     79    private List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
     80    private DialogsPanel dialogsPanel = new DialogsPanel();
    7981
    8082    public final ButtonGroup toolGroup = new ButtonGroup();
    8183   
     
    106108        toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
    107109
    108110        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
    109                              mapView, toggleDialogs);
     111                             mapView, dialogsPanel);
    110112
    111113        /**
    112114         * All additional space goes to the mapView
     
    129131       
    130132        add(splitPane, BorderLayout.CENTER);
    131133
    132         toggleDialogs.setLayout(new BoxLayout(toggleDialogs, BoxLayout.Y_AXIS));
    133         toggleDialogs.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
     134        dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
     135        dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
    134136                                                                       
    135         toggleDialogs.setMinimumSize(new Dimension(24, 0));
     137        dialogsPanel.setMinimumSize(new Dimension(24, 0));
    136138        mapView.setMinimumSize(new Dimension(10,0));
    137139
    138140        toolBarToggle.setFloatable(false);
     
    172174     * Delegates the call to all Destroyables within this component (e.g. MapModes)
    173175     */
    174176    public void destroy() {
    175         for (ToggleDialog t : allDialogs) {
    176             t.closeDetachedDialog();
    177         }
     177        dialogsPanel.destroy();
    178178        for (int i = 0; i < toolBarActions.getComponentCount(); ++i)
    179179            if (toolBarActions.getComponent(i) instanceof Destroyable) {
    180180                ((Destroyable)toolBarActions).destroy();
     
    202202    /**
    203203     * Open all ToggleDialogs that have their preferences property set. Close all others.
    204204     */
    205     public void setVisibleDialogs() {
    206         toggleDialogs.removeAll();
    207         for (ToggleDialog dialog: allDialogs) {
    208             dialog.setVisible(false);
    209             toggleDialogs.add(dialog);
    210             dialog.setParent(toggleDialogs);
    211             if (Main.pref.getBoolean(dialog.getPreferencePrefix()+".visible")) {
    212                 dialog.showDialog();
    213             } else {
    214                 dialog.hideDialog();
    215             }
    216         }
     205    public void initializeDialogsPane() {
     206        dialogsPanel.initialize(allDialogs);
    217207    }
    218208
    219209    /**
     
    243233        }
    244234    }
    245235
    246 
    247 
    248236    /**
    249237     * Change the operating map mode for the view. Will call unregister on the
    250238     * old MapMode and register on the new one.
     
    298286     *
    299287     */
    300288    public <T> T getToggleDialog(Class<T> type) {
    301         for (ToggleDialog td : allDialogs) {
    302             if (type.isInstance(td))
    303                 return type.cast(td);
    304         }
    305         return null;
     289        return dialogsPanel.getToggleDialog(type);
    306290    }
    307291
    308292    /**
    309293     * Returns the current width of the (possibly resized) toggle dialog area
    310294     */
    311295    public int getToggleDlgWidth() {
    312         return toggleDialogs.getWidth();
     296        return dialogsPanel.getWidth();
    313297    }
    314298}
  • src/org/openstreetmap/josm/Main.java

     
    219219            setMapFrame(mapFrame);
    220220            mapFrame.selectMapMode((MapMode)mapFrame.getDefaultButtonAction());
    221221            mapFrame.setVisible(true);
    222             mapFrame.setVisibleDialogs();
     222            mapFrame.initializeDialogsPane();
    223223            // bootstrapping problem: make sure the layer list dialog is going to
    224224            // listen to change events of the very first layer
    225225            //
  • src/org/openstreetmap/josm/data/Preferences.java

     
    441441
    442442    public final void resetToDefault(){
    443443        properties.clear();
    444         put("layerlist.visible", true);
    445         put("propertiesdialog.visible", true);
    446         put("selectionlist.visible", true);
    447         put("commandstack.visible", true);
    448444        if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") == -1) {
    449445            put("laf", "javax.swing.plaf.metal.MetalLookAndFeel");
    450446        } else {