source: josm/trunk/src/org/openstreetmap/josm/gui/widgets/MultiSplitPane.java @ 6340

Last change on this file since 6340 was 6340, checked in by Don-vip, 5 years ago

refactor of some GUI/widgets classes (impacts some plugins):

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