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

Last change on this file since 9067 was 9059, checked in by Don-vip, 8 years ago

checkstyle

  • Property svn:eol-style set to native
File size: 16.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import java.awt.Color;
5import java.awt.Graphics2D;
6import java.awt.Point;
7import java.awt.Polygon;
8import java.awt.Rectangle;
9import java.awt.event.InputEvent;
10import java.awt.event.MouseEvent;
11import java.awt.event.MouseListener;
12import java.awt.event.MouseMotionListener;
13import java.beans.PropertyChangeEvent;
14import java.beans.PropertyChangeListener;
15import java.util.Collection;
16import java.util.LinkedList;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.actions.SelectByInternalPointAction;
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
25import org.openstreetmap.josm.gui.layer.MapViewPaintable;
26import org.openstreetmap.josm.tools.Utils;
27
28/**
29 * Manages the selection of a rectangle or a lasso loop. Listening to left and right mouse button
30 * presses and to mouse motions and draw the rectangle accordingly.
31 *
32 * Left mouse button selects a rectangle from the press until release. Pressing
33 * right mouse button while left is still pressed enable the selection area to move
34 * around. Releasing the left button fires an action event to the listener given
35 * at constructor, except if the right is still pressed, which just remove the
36 * selection rectangle and does nothing.
37 *
38 * It is possible to switch between lasso selection and rectangle selection by using {@link #setLassoMode(boolean)}.
39 *
40 * The point where the left mouse button was pressed and the current mouse
41 * position are two opposite corners of the selection rectangle.
42 *
43 * For rectangle mode, it is possible to specify an aspect ratio (width per height) which the
44 * selection rectangle always must have. In this case, the selection rectangle
45 * will be the largest window with this aspect ratio, where the position the left
46 * mouse button was pressed and the corner of the current mouse position are at
47 * opposite sites (the mouse position corner is the corner nearest to the mouse
48 * cursor).
49 *
50 * When the left mouse button was released, an ActionEvent is send to the
51 * ActionListener given at constructor. The source of this event is this manager.
52 *
53 * @author imi
54 */
55public class SelectionManager implements MouseListener, MouseMotionListener, PropertyChangeListener {
56
57 /**
58 * This is the interface that an user of SelectionManager has to implement
59 * to get informed when a selection closes.
60 * @author imi
61 */
62 public interface SelectionEnded {
63 /**
64 * Called, when the left mouse button was released.
65 * @param r The rectangle that encloses the current selection.
66 * @param e The mouse event.
67 * @see InputEvent#getModifiersEx()
68 * @see SelectionManager#getSelectedObjects(boolean)
69 */
70 void selectionEnded(Rectangle r, MouseEvent e);
71
72 /**
73 * Called to register the selection manager for "active" property.
74 * @param listener The listener to register
75 */
76 void addPropertyChangeListener(PropertyChangeListener listener);
77
78 /**
79 * Called to remove the selection manager from the listener list
80 * for "active" property.
81 * @param listener The listener to register
82 */
83 void removePropertyChangeListener(PropertyChangeListener listener);
84 }
85
86 /**
87 * This draws the selection hint (rectangle or lasso polygon) on the screen.
88 *
89 * @author Michael Zangl
90 */
91 private class SelectionHintLayer implements MapViewPaintable {
92 @Override
93 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
94 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
95 return;
96 Color color = Utils.complement(PaintColors.getBackgroundColor());
97 g.setColor(color);
98 if (lassoMode) {
99 g.drawPolygon(lasso);
100
101 g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 8));
102 g.fillPolygon(lasso);
103 } else {
104 Rectangle paintRect = getSelectionRectangle();
105 g.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
106 }
107 }
108 }
109
110 /**
111 * The listener that receives the events after left mouse button is released.
112 */
113 private final SelectionEnded selectionEndedListener;
114 /**
115 * Position of the map when the mouse button was pressed.
116 * If this is not <code>null</code>, a rectangle/lasso line is drawn on screen.
117 * If this is <code>null</code>, no selection is active.
118 */
119 private Point mousePosStart;
120 /**
121 * The last position of the mouse while the mouse button was pressed.
122 */
123 private Point mousePos;
124 /**
125 * The Component that provides us with OSM data and the aspect is taken from.
126 */
127 private final NavigatableComponent nc;
128 /**
129 * Whether the selection rectangle must obtain the aspect ratio of the
130 * drawComponent.
131 */
132 private boolean aspectRatio;
133
134 /**
135 * <code>true</code> if we should paint a lasso instead of a rectangle.
136 */
137 private boolean lassoMode;
138 /**
139 * The polygon to store the selection outline if {@link #lassoMode} is used.
140 */
141 private Polygon lasso = new Polygon();
142
143 /**
144 * The result of the last selection.
145 */
146 private Polygon selectionResult = new Polygon();
147
148 private final SelectionHintLayer selectionHintLayer = new SelectionHintLayer();
149
150 /**
151 * Create a new SelectionManager.
152 *
153 * @param selectionEndedListener The action listener that receives the event when
154 * the left button is released.
155 * @param aspectRatio If true, the selection window must obtain the aspect
156 * ratio of the drawComponent.
157 * @param navComp The component that provides us with OSM data and the aspect is taken from.
158 */
159 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) {
160 this.selectionEndedListener = selectionEndedListener;
161 this.aspectRatio = aspectRatio;
162 this.nc = navComp;
163 }
164
165 /**
166 * Register itself at the given event source and add a hint layer.
167 * @param eventSource The emitter of the mouse events.
168 * @param lassoMode {@code true} to enable lasso mode, {@code false} to disable it.
169 */
170 public void register(MapView eventSource, boolean lassoMode) {
171 this.lassoMode = lassoMode;
172 eventSource.addMouseListener(this);
173 eventSource.addMouseMotionListener(this);
174 selectionEndedListener.addPropertyChangeListener(this);
175 eventSource.addPropertyChangeListener("scale", new PropertyChangeListener() {
176 @Override
177 public void propertyChange(PropertyChangeEvent evt) {
178 abortSelecting();
179 }
180 });
181 eventSource.addTemporaryLayer(selectionHintLayer);
182 }
183
184 /**
185 * Unregister itself from the given event source and hide the selection hint layer.
186 *
187 * @param eventSource The emitter of the mouse events.
188 */
189 public void unregister(MapView eventSource) {
190 abortSelecting();
191 eventSource.removeTemporaryLayer(selectionHintLayer);
192 eventSource.removeMouseListener(this);
193 eventSource.removeMouseMotionListener(this);
194 selectionEndedListener.removePropertyChangeListener(this);
195 }
196
197 /**
198 * If the correct button, from the "drawing rectangle" mode
199 */
200 @Override
201 public void mousePressed(MouseEvent e) {
202 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1 && Main.main.getCurrentDataSet() != null) {
203 SelectByInternalPointAction.performSelection(Main.map.mapView.getEastNorth(e.getX(), e.getY()),
204 (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) > 0,
205 (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
206 } else if (e.getButton() == MouseEvent.BUTTON1) {
207 mousePosStart = mousePos = e.getPoint();
208
209 lasso.reset();
210 lasso.addPoint(mousePosStart.x, mousePosStart.y);
211 }
212 }
213
214 /**
215 * If the correct button is hold, draw the rectangle.
216 */
217 @Override
218 public void mouseDragged(MouseEvent e) {
219 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
220
221 if (buttonPressed != 0) {
222 if (mousePosStart == null) {
223 mousePosStart = mousePos = e.getPoint();
224 }
225 selectionAreaChanged();
226 }
227
228 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
229 mousePos = e.getPoint();
230 addLassoPoint(e.getPoint());
231 selectionAreaChanged();
232 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
233 moveSelection(e.getX()-mousePos.x, e.getY()-mousePos.y);
234 mousePos = e.getPoint();
235 selectionAreaChanged();
236 }
237 }
238
239 /**
240 * Moves the current selection by some pixels.
241 * @param dx How much to move it in x direction.
242 * @param dy How much to move it in y direction.
243 */
244 private void moveSelection(int dx, int dy) {
245 mousePosStart.x += dx;
246 mousePosStart.y += dy;
247 lasso.translate(dx, dy);
248 }
249
250 /**
251 * Check the state of the keys and buttons and set the selection accordingly.
252 */
253 @Override
254 public void mouseReleased(MouseEvent e) {
255 if (e.getButton() == MouseEvent.BUTTON1) {
256 endSelecting(e);
257 }
258 }
259
260 /**
261 * Ends the selection of the current area. This simulates a release of mouse button 1.
262 * @param e A mouse event that caused this. Needed for backward compatibility.
263 */
264 public void endSelecting(MouseEvent e) {
265 mousePos = e.getPoint();
266 if (lassoMode) {
267 addLassoPoint(e.getPoint());
268 }
269
270 // Left mouse was released while right is still pressed.
271 boolean rightMouseStillPressed = (e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0;
272
273 if (!rightMouseStillPressed) {
274 selectingDone(e);
275 }
276 abortSelecting();
277 }
278
279 private void addLassoPoint(Point point) {
280 if (isNoSelection()) {
281 return;
282 }
283 lasso.addPoint(point.x, point.y);
284 }
285
286 private boolean isNoSelection() {
287 return mousePos == null || mousePosStart == null || mousePos == mousePosStart;
288 }
289
290 /**
291 * Calculate and return the current selection rectangle
292 * @return A rectangle that spans from mousePos to mouseStartPos
293 */
294 private Rectangle getSelectionRectangle() {
295 int x = mousePosStart.x;
296 int y = mousePosStart.y;
297 int w = mousePos.x - mousePosStart.x;
298 int h = mousePos.y - mousePosStart.y;
299 if (w < 0) {
300 x += w;
301 w = -w;
302 }
303 if (h < 0) {
304 y += h;
305 h = -h;
306 }
307
308 if (aspectRatio) {
309 /* Keep the aspect ratio by growing the rectangle; the
310 * rectangle is always under the cursor. */
311 double aspectRatio = (double) nc.getWidth()/nc.getHeight();
312 if ((double) w/h < aspectRatio) {
313 int neww = (int) (h*aspectRatio);
314 if (mousePos.x < mousePosStart.x) {
315 x += w - neww;
316 }
317 w = neww;
318 } else {
319 int newh = (int) (w/aspectRatio);
320 if (mousePos.y < mousePosStart.y) {
321 y += h - newh;
322 }
323 h = newh;
324 }
325 }
326
327 return new Rectangle(x, y, w, h);
328 }
329
330 /**
331 * If the action goes inactive, remove the selection rectangle from screen
332 */
333 @Override
334 public void propertyChange(PropertyChangeEvent evt) {
335 if ("active".equals(evt.getPropertyName()) && !(Boolean) evt.getNewValue()) {
336 abortSelecting();
337 }
338 }
339
340 /**
341 * Stores the current selection and stores the result in {@link #selectionResult} to be retrieved by
342 * {@link #getSelectedObjects(boolean)} later.
343 * @param e The mouse event that caused the selection to be finished.
344 */
345 private void selectingDone(MouseEvent e) {
346 if (isNoSelection()) {
347 // Nothing selected.
348 return;
349 }
350 Rectangle r;
351 if (lassoMode) {
352 r = lasso.getBounds();
353
354 selectionResult = new Polygon(lasso.xpoints, lasso.ypoints, lasso.npoints);
355 } else {
356 r = getSelectionRectangle();
357
358 selectionResult = rectToPolygon(r);
359 }
360 selectionEndedListener.selectionEnded(r, e);
361 }
362
363 private void abortSelecting() {
364 if (mousePosStart != null) {
365 mousePos = mousePosStart = null;
366 lasso.reset();
367 selectionAreaChanged();
368 }
369 }
370
371 private static void selectionAreaChanged() {
372 // Trigger a redraw of the map view.
373 // A nicer way would be to provide change events for the temporary layer.
374 Main.map.mapView.repaint();
375 }
376
377 /**
378 * Return a list of all objects in the active/last selection, respecting the different
379 * modifier.
380 *
381 * @param alt Whether the alt key was pressed, which means select all
382 * objects that are touched, instead those which are completely covered.
383 * @return The collection of selected objects.
384 */
385 public Collection<OsmPrimitive> getSelectedObjects(boolean alt) {
386
387 Collection<OsmPrimitive> selection = new LinkedList<>();
388
389 // whether user only clicked, not dragged.
390 boolean clicked = false;
391 Rectangle bounding = selectionResult.getBounds();
392 if (bounding.height <= 2 && bounding.width <= 2) {
393 clicked = true;
394 }
395
396 if (clicked) {
397 Point center = new Point(selectionResult.xpoints[0], selectionResult.ypoints[0]);
398 OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false);
399 if (osm != null) {
400 selection.add(osm);
401 }
402 } else {
403 // nodes
404 for (Node n : nc.getCurrentDataSet().getNodes()) {
405 if (n.isSelectable() && selectionResult.contains(nc.getPoint2D(n))) {
406 selection.add(n);
407 }
408 }
409
410 // ways
411 for (Way w : nc.getCurrentDataSet().getWays()) {
412 if (!w.isSelectable() || w.getNodesCount() == 0) {
413 continue;
414 }
415 if (alt) {
416 for (Node n : w.getNodes()) {
417 if (!n.isIncomplete() && selectionResult.contains(nc.getPoint2D(n))) {
418 selection.add(w);
419 break;
420 }
421 }
422 } else {
423 boolean allIn = true;
424 for (Node n : w.getNodes()) {
425 if (!n.isIncomplete() && !selectionResult.contains(nc.getPoint(n))) {
426 allIn = false;
427 break;
428 }
429 }
430 if (allIn) {
431 selection.add(w);
432 }
433 }
434 }
435 }
436 return selection;
437 }
438
439 private static Polygon rectToPolygon(Rectangle r) {
440 Polygon poly = new Polygon();
441
442 poly.addPoint(r.x, r.y);
443 poly.addPoint(r.x, r.y + r.height);
444 poly.addPoint(r.x + r.width, r.y + r.height);
445 poly.addPoint(r.x + r.width, r.y);
446
447 return poly;
448 }
449
450 /**
451 * Enables or disables the lasso mode.
452 * @param lassoMode {@code true} to enable lasso mode, {@code false} to disable it.
453 */
454 public void setLassoMode(boolean lassoMode) {
455 this.lassoMode = lassoMode;
456 }
457
458 @Override
459 public void mouseClicked(MouseEvent e) {}
460
461 @Override
462 public void mouseEntered(MouseEvent e) {}
463
464 @Override
465 public void mouseExited(MouseEvent e) {}
466
467 @Override
468 public void mouseMoved(MouseEvent e) {}
469}
Note: See TracBrowser for help on using the repository browser.