source: josm/trunk/src/org/openstreetmap/josm/gui/SelectionManager.java @ 5241

Revision 5152, 12.8 KB checked in by simon04, 7 weeks ago (diff)

see #3910 - original patch by Oliver Raupach - add lasso selection mode

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui;
3
4import java.awt.Color;
5import java.awt.Component;
6import java.awt.Graphics;
7import java.awt.Point;
8import java.awt.Polygon;
9import java.awt.Rectangle;
10import java.awt.event.InputEvent;
11import java.awt.event.MouseEvent;
12import java.awt.event.MouseListener;
13import java.awt.event.MouseMotionListener;
14import java.beans.PropertyChangeEvent;
15import java.beans.PropertyChangeListener;
16import java.util.Collection;
17import java.util.LinkedList;
18
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Way;
22
23/**
24 * Manages the selection of a rectangle. Listening to left and right mouse button
25 * presses and to mouse motions and draw the rectangle accordingly.
26 *
27 * Left mouse button selects a rectangle from the press until release. Pressing
28 * right mouse button while left is still pressed enable the rectangle to move
29 * around. Releasing the left button fires an action event to the listener given
30 * at constructor, except if the right is still pressed, which just remove the
31 * selection rectangle and does nothing.
32 *
33 * The point where the left mouse button was pressed and the current mouse
34 * position are two opposite corners of the selection rectangle.
35 *
36 * It is possible to specify an aspect ratio (width per height) which the
37 * selection rectangle always must have. In this case, the selection rectangle
38 * will be the largest window with this aspect ratio, where the position the left
39 * mouse button was pressed and the corner of the current mouse position are at
40 * opposite sites (the mouse position corner is the corner nearest to the mouse
41 * cursor).
42 *
43 * When the left mouse button was released, an ActionEvent is send to the
44 * ActionListener given at constructor. The source of this event is this manager.
45 *
46 * @author imi
47 */
48public class SelectionManager implements MouseListener, MouseMotionListener, PropertyChangeListener {
49
50    /**
51     * This is the interface that an user of SelectionManager has to implement
52     * to get informed when a selection closes.
53     * @author imi
54     */
55    public interface SelectionEnded {
56        /**
57         * Called, when the left mouse button was released.
58         * @param r The rectangle that is currently the selection.
59         * @param alt Whether the alt key was pressed
60         * @param shift Whether the shift key was pressed
61         * @param ctrl Whether the ctrl key was pressed
62         * @see InputEvent#getModifiersEx()
63         */
64        public void selectionEnded(Rectangle r, MouseEvent e);
65        /**
66         * Called to register the selection manager for "active" property.
67         * @param listener The listener to register
68         */
69        public void addPropertyChangeListener(PropertyChangeListener listener);
70        /**
71         * Called to remove the selection manager from the listener list
72         * for "active" property.
73         * @param listener The listener to register
74         */
75        public void removePropertyChangeListener(PropertyChangeListener listener);
76    }
77    /**
78     * The listener that receives the events after left mouse button is released.
79     */
80    private final SelectionEnded selectionEndedListener;
81    /**
82     * Position of the map when the mouse button was pressed.
83     * If this is not <code>null</code>, a rectangle is drawn on screen.
84     */
85    private Point mousePosStart;
86    /**
87     * Position of the map when the selection rectangle was last drawn.
88     */
89    private Point mousePos;
90    /**
91     * The Component, the selection rectangle is drawn onto.
92     */
93    private final NavigatableComponent nc;
94    /**
95     * Whether the selection rectangle must obtain the aspect ratio of the
96     * drawComponent.
97     */
98    private boolean aspectRatio;
99
100    private boolean lassoMode;
101    private Polygon lasso = new Polygon();
102
103    /**
104     * Create a new SelectionManager.
105     *
106     * @param selectionEndedListener The action listener that receives the event when
107     *      the left button is released.
108     * @param aspectRatio If true, the selection window must obtain the aspect
109     *      ratio of the drawComponent.
110     * @param navComp The component, the rectangle is drawn onto.
111     */
112    public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) {
113        this.selectionEndedListener = selectionEndedListener;
114        this.aspectRatio = aspectRatio;
115        this.nc = navComp;
116    }
117
118    /**
119     * Register itself at the given event source.
120     * @param eventSource The emitter of the mouse events.
121     */
122    public void register(NavigatableComponent eventSource, boolean lassoMode) {
123       this.lassoMode = lassoMode;
124        eventSource.addMouseListener(this);
125        eventSource.addMouseMotionListener(this);
126        selectionEndedListener.addPropertyChangeListener(this);
127        eventSource.addPropertyChangeListener("scale", new PropertyChangeListener(){
128            public void propertyChange(PropertyChangeEvent evt) {
129                if (mousePosStart != null) {
130                    paintRect();
131                    mousePos = mousePosStart = null;
132                }
133            }
134        });
135    }
136    /**
137     * Unregister itself from the given event source. If a selection rectangle is
138     * shown, hide it first.
139     *
140     * @param eventSource The emitter of the mouse events.
141     */
142    public void unregister(Component eventSource) {
143        eventSource.removeMouseListener(this);
144        eventSource.removeMouseMotionListener(this);
145        selectionEndedListener.removePropertyChangeListener(this);
146    }
147
148    /**
149     * If the correct button, from the "drawing rectangle" mode
150     */
151    public void mousePressed(MouseEvent e) {
152        if (e.getButton() == MouseEvent.BUTTON1) {
153            mousePosStart = mousePos = e.getPoint();
154
155            lasso.reset();
156            lasso.addPoint(mousePosStart.x, mousePosStart.y);
157        }
158    }
159
160    /**
161     * If the correct button is hold, draw the rectangle.
162     */
163    public void mouseDragged(MouseEvent e) {
164        int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
165
166        if (buttonPressed != 0) {
167            if (mousePosStart == null) {
168                mousePosStart = mousePos = e.getPoint();
169            }
170            if (!lassoMode) {
171                paintRect();
172            }
173        }
174
175        if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
176            mousePos = e.getPoint();
177            if (lassoMode) {
178                paintLasso();
179            } else {
180                paintRect();
181            }
182        } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
183            mousePosStart.x += e.getX()-mousePos.x;
184            mousePosStart.y += e.getY()-mousePos.y;
185            mousePos = e.getPoint();
186            paintRect();
187        }
188    }
189
190    /**
191     * Check the state of the keys and buttons and set the selection accordingly.
192     */
193    public void mouseReleased(MouseEvent e) {
194        if (e.getButton() != MouseEvent.BUTTON1)
195            return;
196        if (mousePos == null || mousePosStart == null)
197            return; // injected release from outside
198        // disable the selection rect
199        Rectangle r;
200        if (!lassoMode) {
201            paintRect();
202            r = getSelectionRectangle();
203
204            lasso = rectToPolygon(r);
205        } else {
206            lasso.addPoint(mousePos.x, mousePos.y);
207            r = lasso.getBounds();
208        }
209        mousePosStart = null;
210        mousePos = null;
211
212        if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0) {
213            selectionEndedListener.selectionEnded(r, e);
214        }
215    }
216
217    /**
218     * Draw a selection rectangle on screen. If already a rectangle is drawn,
219     * it is removed instead.
220     */
221    private void paintRect() {
222        if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
223            return;
224        Graphics g = nc.getGraphics();
225        g.setColor(Color.BLACK);
226        g.setXORMode(Color.WHITE);
227
228        Rectangle r = getSelectionRectangle();
229        g.drawRect(r.x,r.y,r.width,r.height);
230    }
231
232    private void paintLasso() {
233        if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) {
234            return;
235        }
236
237        Graphics g = nc.getGraphics();
238        g.setColor(Color.WHITE);
239
240        int lastPosX = lasso.xpoints[lasso.npoints - 1];
241        int lastPosY = lasso.ypoints[lasso.npoints - 1];
242        g.drawLine(lastPosX, lastPosY, mousePos.x, mousePos.y);
243
244        lasso.addPoint(mousePos.x, mousePos.y);
245    }
246
247    /**
248     * Calculate and return the current selection rectangle
249     * @return A rectangle that spans from mousePos to mouseStartPos
250     */
251    private Rectangle getSelectionRectangle() {
252        int x = mousePosStart.x;
253        int y = mousePosStart.y;
254        int w = mousePos.x - mousePosStart.x;
255        int h = mousePos.y - mousePosStart.y;
256        if (w < 0) {
257            x += w;
258            w = -w;
259        }
260        if (h < 0) {
261            y += h;
262            h = -h;
263        }
264
265        if (aspectRatio) {
266            /* Keep the aspect ratio by growing the rectangle; the
267             * rectangle is always under the cursor. */
268            double aspectRatio = (double)nc.getWidth()/nc.getHeight();
269            if ((double)w/h < aspectRatio) {
270                int neww = (int)(h*aspectRatio);
271                if (mousePos.x < mousePosStart.x) {
272                    x += w - neww;
273                }
274                w = neww;
275            } else {
276                int newh = (int)(w/aspectRatio);
277                if (mousePos.y < mousePosStart.y) {
278                    y += h - newh;
279                }
280                h = newh;
281            }
282        }
283
284        return new Rectangle(x,y,w,h);
285    }
286
287    /**
288     * If the action goes inactive, remove the selection rectangle from screen
289     */
290    public void propertyChange(PropertyChangeEvent evt) {
291        if (evt.getPropertyName().equals("active") && !(Boolean)evt.getNewValue() && mousePosStart != null) {
292            paintRect();
293            mousePosStart = null;
294            mousePos = null;
295        }
296    }
297
298    /**
299     * Return a list of all objects in the selection, respecting the different
300     * modifier.
301     *
302     * @param alt Whether the alt key was pressed, which means select all
303     * objects that are touched, instead those which are completely covered.
304     */
305    public Collection<OsmPrimitive> getSelectedObjects(boolean alt) {
306
307        Collection<OsmPrimitive> selection = new LinkedList<OsmPrimitive>();
308
309        // whether user only clicked, not dragged.
310        boolean clicked = false;
311        Rectangle bounding = lasso.getBounds();
312        if (bounding.height <= 2 && bounding.width <= 2) {
313            clicked = true;
314        }
315
316        if (clicked) {
317            Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]);
318            OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false);
319            if (osm != null) {
320                selection.add(osm);
321            }
322        } else {
323            // nodes
324            for (Node n : nc.getCurrentDataSet().getNodes()) {
325                if (n.isSelectable() && lasso.contains(nc.getPoint(n))) {
326                    selection.add(n);
327                }
328            }
329
330            // ways
331            for (Way w : nc.getCurrentDataSet().getWays()) {
332                if (!w.isSelectable() || w.getNodesCount() == 0) {
333                    continue;
334                }
335                if (alt) {
336                    for (Node n : w.getNodes()) {
337                        if (!n.isIncomplete() && lasso.contains(nc.getPoint(n))) {
338                            selection.add(w);
339                            break;
340                        }
341                    }
342                } else {
343                    boolean allIn = true;
344                    for (Node n : w.getNodes()) {
345                        if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) {
346                            allIn = false;
347                            break;
348                        }
349                    }
350                    if (allIn) {
351                        selection.add(w);
352                    }
353                }
354            }
355        }
356        return selection;
357    }
358
359    private Polygon rectToPolygon(Rectangle r) {
360        Polygon poly = new Polygon();
361
362        poly.addPoint(r.x, r.y);
363        poly.addPoint(r.x, r.y + r.height);
364        poly.addPoint(r.x + r.width, r.y + r.height);
365        poly.addPoint(r.x + r.width, r.y);
366
367        return poly;
368    }
369
370    public void setLassoMode(boolean lassoMode) {
371        this.lassoMode = lassoMode;
372    }
373
374    public void mouseClicked(MouseEvent e) {}
375    public void mouseEntered(MouseEvent e) {}
376    public void mouseExited(MouseEvent e) {}
377    public void mouseMoved(MouseEvent e) {}
378}
Note: See TracBrowser for help on using the repository browser.