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

Last change on this file since 8394 was 7921, checked in by Don-vip, 9 years ago

fix #10921 - NPE

  • Property svn:eol-style set to native
File size: 13.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import java.awt.Component;
5import java.awt.Point;
6import java.awt.Polygon;
7import java.awt.Rectangle;
8import java.awt.event.InputEvent;
9import java.awt.event.MouseEvent;
10import java.awt.event.MouseListener;
11import java.awt.event.MouseMotionListener;
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.Collection;
15import java.util.LinkedList;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.actions.SelectByInternalPointAction;
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 e The mouse event.
60 * @see InputEvent#getModifiersEx()
61 */
62 public void selectionEnded(Rectangle r, MouseEvent e);
63 /**
64 * Called to register the selection manager for "active" property.
65 * @param listener The listener to register
66 */
67 public void addPropertyChangeListener(PropertyChangeListener listener);
68 /**
69 * Called to remove the selection manager from the listener list
70 * for "active" property.
71 * @param listener The listener to register
72 */
73 public void removePropertyChangeListener(PropertyChangeListener listener);
74 }
75 /**
76 * The listener that receives the events after left mouse button is released.
77 */
78 private final SelectionEnded selectionEndedListener;
79 /**
80 * Position of the map when the mouse button was pressed.
81 * If this is not <code>null</code>, a rectangle is drawn on screen.
82 */
83 private Point mousePosStart;
84 /**
85 * Position of the map when the selection rectangle was last drawn.
86 */
87 private Point mousePos;
88 /**
89 * The Component, the selection rectangle is drawn onto.
90 */
91 private final NavigatableComponent nc;
92 /**
93 * Whether the selection rectangle must obtain the aspect ratio of the
94 * drawComponent.
95 */
96 private boolean aspectRatio;
97
98 private boolean lassoMode;
99 private Polygon lasso = new Polygon();
100
101 /**
102 * Create a new SelectionManager.
103 *
104 * @param selectionEndedListener The action listener that receives the event when
105 * the left button is released.
106 * @param aspectRatio If true, the selection window must obtain the aspect
107 * ratio of the drawComponent.
108 * @param navComp The component, the rectangle is drawn onto.
109 */
110 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) {
111 this.selectionEndedListener = selectionEndedListener;
112 this.aspectRatio = aspectRatio;
113 this.nc = navComp;
114 }
115
116 /**
117 * Register itself at the given event source.
118 * @param eventSource The emitter of the mouse events.
119 * @param lassoMode {@code true} to enable lasso mode, {@code false} to disable it.
120 */
121 public void register(NavigatableComponent eventSource, boolean lassoMode) {
122 this.lassoMode = lassoMode;
123 eventSource.addMouseListener(this);
124 eventSource.addMouseMotionListener(this);
125 selectionEndedListener.addPropertyChangeListener(this);
126 eventSource.addPropertyChangeListener("scale", new PropertyChangeListener(){
127 @Override
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 @Override
152 public void mousePressed(MouseEvent e) {
153 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1 && Main.main.getCurrentDataSet() != null) {
154 SelectByInternalPointAction.performSelection(Main.map.mapView.getEastNorth(e.getX(), e.getY()),
155 (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) > 0,
156 (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
157 } else if (e.getButton() == MouseEvent.BUTTON1) {
158 mousePosStart = mousePos = e.getPoint();
159
160 lasso.reset();
161 lasso.addPoint(mousePosStart.x, mousePosStart.y);
162 }
163 }
164
165 /**
166 * If the correct button is hold, draw the rectangle.
167 */
168 @Override
169 public void mouseDragged(MouseEvent e) {
170 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
171
172 if (buttonPressed != 0) {
173 if (mousePosStart == null) {
174 mousePosStart = mousePos = e.getPoint();
175 }
176 if (!lassoMode) {
177 paintRect();
178 }
179 }
180
181 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
182 mousePos = e.getPoint();
183 if (lassoMode) {
184 paintLasso();
185 } else {
186 paintRect();
187 }
188 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
189 mousePosStart.x += e.getX()-mousePos.x;
190 mousePosStart.y += e.getY()-mousePos.y;
191 mousePos = e.getPoint();
192 paintRect();
193 }
194 }
195
196 /**
197 * Check the state of the keys and buttons and set the selection accordingly.
198 */
199 @Override
200 public void mouseReleased(MouseEvent e) {
201 if (e.getButton() != MouseEvent.BUTTON1)
202 return;
203 if (mousePos == null || mousePosStart == null)
204 return; // injected release from outside
205 // disable the selection rect
206 Rectangle r;
207 if (!lassoMode) {
208 nc.requestClearRect();
209 r = getSelectionRectangle();
210
211 lasso = rectToPolygon(r);
212 } else {
213 nc.requestClearPoly();
214 lasso.addPoint(mousePos.x, mousePos.y);
215 r = lasso.getBounds();
216 }
217 mousePosStart = null;
218 mousePos = null;
219
220 if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0) {
221 selectionEndedListener.selectionEnded(r, e);
222 }
223 }
224
225 /**
226 * Draws a selection rectangle on screen.
227 */
228 private void paintRect() {
229 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
230 return;
231 nc.requestPaintRect(getSelectionRectangle());
232 }
233
234 private void paintLasso() {
235 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) {
236 return;
237 }
238 lasso.addPoint(mousePos.x, mousePos.y);
239 nc.requestPaintPoly(lasso);
240 }
241
242 /**
243 * Calculate and return the current selection rectangle
244 * @return A rectangle that spans from mousePos to mouseStartPos
245 */
246 private Rectangle getSelectionRectangle() {
247 int x = mousePosStart.x;
248 int y = mousePosStart.y;
249 int w = mousePos.x - mousePosStart.x;
250 int h = mousePos.y - mousePosStart.y;
251 if (w < 0) {
252 x += w;
253 w = -w;
254 }
255 if (h < 0) {
256 y += h;
257 h = -h;
258 }
259
260 if (aspectRatio) {
261 /* Keep the aspect ratio by growing the rectangle; the
262 * rectangle is always under the cursor. */
263 double aspectRatio = (double)nc.getWidth()/nc.getHeight();
264 if ((double)w/h < aspectRatio) {
265 int neww = (int)(h*aspectRatio);
266 if (mousePos.x < mousePosStart.x) {
267 x += w - neww;
268 }
269 w = neww;
270 } else {
271 int newh = (int)(w/aspectRatio);
272 if (mousePos.y < mousePosStart.y) {
273 y += h - newh;
274 }
275 h = newh;
276 }
277 }
278
279 return new Rectangle(x,y,w,h);
280 }
281
282 /**
283 * If the action goes inactive, remove the selection rectangle from screen
284 */
285 @Override
286 public void propertyChange(PropertyChangeEvent evt) {
287 if ("active".equals(evt.getPropertyName()) && !(Boolean)evt.getNewValue() && mousePosStart != null) {
288 paintRect();
289 mousePosStart = null;
290 mousePos = null;
291 }
292 }
293
294 /**
295 * Return a list of all objects in the selection, respecting the different
296 * modifier.
297 *
298 * @param alt Whether the alt key was pressed, which means select all
299 * objects that are touched, instead those which are completely covered.
300 * @return The collection of selected objects.
301 */
302 public Collection<OsmPrimitive> getSelectedObjects(boolean alt) {
303
304 Collection<OsmPrimitive> selection = new LinkedList<>();
305
306 // whether user only clicked, not dragged.
307 boolean clicked = false;
308 Rectangle bounding = lasso.getBounds();
309 if (bounding.height <= 2 && bounding.width <= 2) {
310 clicked = true;
311 }
312
313 if (clicked) {
314 Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]);
315 OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false);
316 if (osm != null) {
317 selection.add(osm);
318 }
319 } else {
320 // nodes
321 for (Node n : nc.getCurrentDataSet().getNodes()) {
322 if (n.isSelectable() && lasso.contains(nc.getPoint2D(n))) {
323 selection.add(n);
324 }
325 }
326
327 // ways
328 for (Way w : nc.getCurrentDataSet().getWays()) {
329 if (!w.isSelectable() || w.getNodesCount() == 0) {
330 continue;
331 }
332 if (alt) {
333 for (Node n : w.getNodes()) {
334 if (!n.isIncomplete() && lasso.contains(nc.getPoint2D(n))) {
335 selection.add(w);
336 break;
337 }
338 }
339 } else {
340 boolean allIn = true;
341 for (Node n : w.getNodes()) {
342 if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) {
343 allIn = false;
344 break;
345 }
346 }
347 if (allIn) {
348 selection.add(w);
349 }
350 }
351 }
352 }
353 return selection;
354 }
355
356 private Polygon rectToPolygon(Rectangle r) {
357 Polygon poly = new Polygon();
358
359 poly.addPoint(r.x, r.y);
360 poly.addPoint(r.x, r.y + r.height);
361 poly.addPoint(r.x + r.width, r.y + r.height);
362 poly.addPoint(r.x + r.width, r.y);
363
364 return poly;
365 }
366
367 /**
368 * Enables or disables the lasso mode.
369 * @param lassoMode {@code true} to enable lasso mode, {@code false} to disable it.
370 */
371 public void setLassoMode(boolean lassoMode) {
372 this.lassoMode = lassoMode;
373 }
374
375 @Override
376 public void mouseClicked(MouseEvent e) {}
377 @Override
378 public void mouseEntered(MouseEvent e) {}
379 @Override
380 public void mouseExited(MouseEvent e) {}
381 @Override
382 public void mouseMoved(MouseEvent e) {}
383}
Note: See TracBrowser for help on using the repository browser.