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

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

Sonar/Findbugs - Avoid commented-out lines of code, javadoc

  • 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;
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;
31import javax.accessibility.AccessibleContext;
32import javax.accessibility.AccessibleRole;
33import javax.swing.JPanel;
34import javax.swing.event.MouseInputAdapter;
35
36import org.openstreetmap.josm.gui.MultiSplitLayout.Divider;
37import org.openstreetmap.josm.gui.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 - SwingX
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        @Override
158        public void paint(Graphics g, Divider divider) {
159            if ((divider == activeDivider()) && !isContinuousLayout()) {
160                Graphics2D g2d = (Graphics2D)g;
161                g2d.setColor(Color.black);
162                g2d.fill(divider.getBounds());
163            }
164        }
165    }
166
167    /**
168     * The DividerPainter that's used to paint Dividers on this MultiSplitPane.
169     * This property may be null.
170     *
171     * @return the value of the dividerPainter Property
172     * @see #setDividerPainter
173     */
174    public DividerPainter getDividerPainter() {
175        return dividerPainter;
176    }
177
178    /**
179     * Sets the DividerPainter that's used to paint Dividers on this
180     * MultiSplitPane.  The default DividerPainter only draws
181     * the activeDivider (if there is one) and then, only if
182     * continuousLayout is false.  The value of this property is
183     * used by the paintChildren method: Dividers are painted after
184     * the MultiSplitPane's children have been rendered so that
185     * the activeDivider can appear "on top of" the children.
186     *
187     * @param dividerPainter the value of the dividerPainter property, can be null
188     * @see #paintChildren
189     * @see #activeDivider
190     */
191    public void setDividerPainter(DividerPainter dividerPainter) {
192        this.dividerPainter = dividerPainter;
193    }
194
195    /**
196     * Uses the DividerPainter (if any) to paint each Divider that
197     * overlaps the clip Rectangle.  This is done after the call to
198     * <code>super.paintChildren()</code> so that Dividers can be
199     * rendered "on top of" the children.
200     * <p>
201     * {@inheritDoc}
202     */
203    @Override
204    protected void paintChildren(Graphics g) {
205        super.paintChildren(g);
206        DividerPainter dp = getDividerPainter();
207        Rectangle clipR = g.getClipBounds();
208        if ((dp != null) && (clipR != null)) {
209            Graphics dpg = g.create();
210            try {
211                MultiSplitLayout msl = getMultiSplitLayout();
212                for(Divider divider : msl.dividersThatOverlap(clipR)) {
213                    dp.paint(dpg, divider);
214                }
215            }
216            finally {
217                dpg.dispose();
218            }
219        }
220    }
221
222    private boolean dragUnderway = false;
223    private MultiSplitLayout.Divider dragDivider = null;
224    private Rectangle initialDividerBounds = null;
225    private boolean oldFloatingDividers = true;
226    private int dragOffsetX = 0;
227    private int dragOffsetY = 0;
228    private int dragMin = -1;
229    private int dragMax = -1;
230
231    private void startDrag(int mx, int my) {
232        requestFocusInWindow();
233        MultiSplitLayout msl = getMultiSplitLayout();
234        MultiSplitLayout.Divider divider = msl.dividerAt(mx, my);
235        if (divider != null) {
236            MultiSplitLayout.Node prevNode = divider.previousSibling();
237            MultiSplitLayout.Node nextNode = divider.nextSibling();
238            if ((prevNode == null) || (nextNode == null)) {
239                dragUnderway = false;
240            }
241            else {
242                initialDividerBounds = divider.getBounds();
243                dragOffsetX = mx - initialDividerBounds.x;
244                dragOffsetY = my - initialDividerBounds.y;
245                dragDivider  = divider;
246                Rectangle prevNodeBounds = prevNode.getBounds();
247                Rectangle nextNodeBounds = nextNode.getBounds();
248                if (dragDivider.isVertical()) {
249                    dragMin = prevNodeBounds.x;
250                    dragMax = nextNodeBounds.x + nextNodeBounds.width;
251                    dragMax -= dragDivider.getBounds().width;
252                }
253                else {
254                    dragMin = prevNodeBounds.y;
255                    dragMax = nextNodeBounds.y + nextNodeBounds.height;
256                    dragMax -= dragDivider.getBounds().height;
257                }
258                oldFloatingDividers = getMultiSplitLayout().getFloatingDividers();
259                getMultiSplitLayout().setFloatingDividers(false);
260                dragUnderway = true;
261            }
262        }
263        else {
264            dragUnderway = false;
265        }
266    }
267
268    private void repaintDragLimits() {
269        Rectangle damageR = dragDivider.getBounds();
270        if (dragDivider.isVertical()) {
271            damageR.x = dragMin;
272            damageR.width = dragMax - dragMin;
273        }
274        else {
275            damageR.y = dragMin;
276            damageR.height = dragMax - dragMin;
277        }
278        repaint(damageR);
279    }
280
281    private void updateDrag(int mx, int my) {
282        if (!dragUnderway) {
283            return;
284        }
285        Rectangle oldBounds = dragDivider.getBounds();
286        Rectangle bounds = new Rectangle(oldBounds);
287        if (dragDivider.isVertical()) {
288            bounds.x = mx - dragOffsetX;
289            bounds.x = Math.max(bounds.x, dragMin);
290            bounds.x = Math.min(bounds.x, dragMax);
291        }
292        else {
293            bounds.y = my - dragOffsetY;
294            bounds.y = Math.max(bounds.y, dragMin);
295            bounds.y = Math.min(bounds.y, dragMax);
296        }
297        dragDivider.setBounds(bounds);
298        if (isContinuousLayout()) {
299            revalidate();
300            repaintDragLimits();
301        }
302        else {
303            repaint(oldBounds.union(bounds));
304        }
305    }
306
307    private void clearDragState() {
308        dragDivider = null;
309        initialDividerBounds = null;
310        oldFloatingDividers = true;
311        dragOffsetX = dragOffsetY = 0;
312        dragMin = dragMax = -1;
313        dragUnderway = false;
314    }
315
316    private void finishDrag(int x, int y) {
317        if (dragUnderway) {
318            clearDragState();
319            if (!isContinuousLayout()) {
320                revalidate();
321                repaint();
322            }
323        }
324    }
325
326    private void cancelDrag() {
327        if (dragUnderway) {
328            dragDivider.setBounds(initialDividerBounds);
329            getMultiSplitLayout().setFloatingDividers(oldFloatingDividers);
330            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
331            repaint();
332            revalidate();
333            clearDragState();
334        }
335    }
336
337    private void updateCursor(int x, int y, boolean show) {
338        if (dragUnderway) {
339            return;
340        }
341        int cursorID = Cursor.DEFAULT_CURSOR;
342        if (show) {
343            MultiSplitLayout.Divider divider = getMultiSplitLayout().dividerAt(x, y);
344            if (divider != null) {
345                cursorID  = (divider.isVertical()) ?
346                    Cursor.E_RESIZE_CURSOR :
347                    Cursor.N_RESIZE_CURSOR;
348            }
349        }
350        setCursor(Cursor.getPredefinedCursor(cursorID));
351    }
352
353    private class InputHandler extends MouseInputAdapter implements KeyListener {
354
355        @Override
356        public void mouseEntered(MouseEvent e) {
357            updateCursor(e.getX(), e.getY(), true);
358        }
359
360        @Override
361        public void mouseMoved(MouseEvent e) {
362            updateCursor(e.getX(), e.getY(), true);
363        }
364
365        @Override
366        public void mouseExited(MouseEvent e) {
367            updateCursor(e.getX(), e.getY(), false);
368        }
369
370        @Override
371        public void mousePressed(MouseEvent e) {
372            startDrag(e.getX(), e.getY());
373        }
374        @Override
375        public void mouseReleased(MouseEvent e) {
376            finishDrag(e.getX(), e.getY());
377        }
378        @Override
379        public void mouseDragged(MouseEvent e) {
380            updateDrag(e.getX(), e.getY());
381        }
382        @Override
383        public void keyPressed(KeyEvent e) {
384            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
385                cancelDrag();
386            }
387        }
388        @Override
389        public void keyReleased(KeyEvent e) { }
390        @Override
391        public void keyTyped(KeyEvent e) { }
392    }
393
394    @Override
395    public AccessibleContext getAccessibleContext() {
396        if( accessibleContext == null ) {
397            accessibleContext = new AccessibleMultiSplitPane();
398        }
399        return accessibleContext;
400    }
401
402    protected class AccessibleMultiSplitPane extends AccessibleJPanel {
403        @Override
404        public AccessibleRole getAccessibleRole() {
405            return AccessibleRole.SPLIT_PANE;
406        }
407    }
408}
Note: See TracBrowser for help on using the repository browser.