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

Last change on this file since 12936 was 12636, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.getLayerManager(). Replacement: gui.MainApplication.getLayerManager()

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