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

Last change on this file since 11483 was 10716, checked in by simon04, 8 years ago

see #11390, see #12890 - Deprecate predicates in OsmPrimitive class

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