From 6f8b8dad2291ebbdafcdb66359ca2bdbdf03c59a Mon Sep 17 00:00:00 2001
From: Michael Zangl <michael.zangl@student.kit.edu>
Date: Wed, 1 Jul 2015 14:22:16 +0200
Subject: [PATCH 3/8] Fixed state errors in selection (rect/lasso) and made
them draw on a temporary layer.
---
.../josm/actions/mapmode/SelectAction.java | 4 +
.../openstreetmap/josm/gui/SelectionManager.java | 217 ++++++++++++++-------
2 files changed, 152 insertions(+), 69 deletions(-)
diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
index 70ad05a..36cfdb7 100644
|
a
|
b
|
public class SelectAction extends MapMode implements ModifierListener, KeyPressR
|
| 585 | 585 | mouseReleaseTime = System.currentTimeMillis(); |
| 586 | 586 | |
| 587 | 587 | if (mode == Mode.SELECT) { |
| | 588 | if (e.getButton() != MouseEvent.BUTTON1) { |
| | 589 | return; |
| | 590 | } |
| | 591 | selectionManager.endSelecting(e); |
| 588 | 592 | selectionManager.unregister(mv); |
| 589 | 593 | |
| 590 | 594 | // Select Draw Tool if no selection has been made |
diff --git a/src/org/openstreetmap/josm/gui/SelectionManager.java b/src/org/openstreetmap/josm/gui/SelectionManager.java
index f50ed98..4a7703a 100644
|
a
|
b
|
|
| 1 | 1 | // License: GPL. For details, see LICENSE file. |
| 2 | 2 | package org.openstreetmap.josm.gui; |
| 3 | 3 | |
| 4 | | import java.awt.Component; |
| | 4 | import java.awt.Color; |
| | 5 | import java.awt.Graphics2D; |
| 5 | 6 | import java.awt.Point; |
| 6 | 7 | import java.awt.Polygon; |
| 7 | 8 | import java.awt.Rectangle; |
| … |
… |
import java.util.LinkedList;
|
| 16 | 17 | |
| 17 | 18 | import org.openstreetmap.josm.Main; |
| 18 | 19 | import org.openstreetmap.josm.actions.SelectByInternalPointAction; |
| | 20 | import org.openstreetmap.josm.data.Bounds; |
| 19 | 21 | import org.openstreetmap.josm.data.osm.Node; |
| 20 | 22 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
| 21 | 23 | import org.openstreetmap.josm.data.osm.Way; |
| | 24 | import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; |
| | 25 | import org.openstreetmap.josm.gui.layer.MapViewPaintable; |
| | 26 | import org.openstreetmap.josm.tools.Utils; |
| 22 | 27 | |
| 23 | 28 | /** |
| 24 | | * Manages the selection of a rectangle. Listening to left and right mouse button |
| | 29 | * Manages the selection of a rectangle or a lasso loop. Listening to left and right mouse button |
| 25 | 30 | * presses and to mouse motions and draw the rectangle accordingly. |
| 26 | 31 | * |
| 27 | 32 | * 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 |
| | 33 | * right mouse button while left is still pressed enable the selection area to move |
| 29 | 34 | * around. Releasing the left button fires an action event to the listener given |
| 30 | 35 | * at constructor, except if the right is still pressed, which just remove the |
| 31 | 36 | * selection rectangle and does nothing. |
| 32 | 37 | * |
| | 38 | * It is possible to switch between lasso selection and rectangle selection by using {@link #setLassoMode(boolean)}. |
| | 39 | * |
| 33 | 40 | * The point where the left mouse button was pressed and the current mouse |
| 34 | 41 | * position are two opposite corners of the selection rectangle. |
| 35 | 42 | * |
| 36 | | * It is possible to specify an aspect ratio (width per height) which the |
| | 43 | * For rectangle mode, it is possible to specify an aspect ratio (width per height) which the |
| 37 | 44 | * selection rectangle always must have. In this case, the selection rectangle |
| 38 | 45 | * will be the largest window with this aspect ratio, where the position the left |
| 39 | 46 | * mouse button was pressed and the corner of the current mouse position are at |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 55 | 62 | public interface SelectionEnded { |
| 56 | 63 | /** |
| 57 | 64 | * Called, when the left mouse button was released. |
| 58 | | * @param r The rectangle that is currently the selection. |
| | 65 | * @param r The rectangle that encloses the current selection. |
| 59 | 66 | * @param e The mouse event. |
| 60 | 67 | * @see InputEvent#getModifiersEx() |
| | 68 | * @see SelectionManager#getSelectedObjects(boolean) |
| 61 | 69 | */ |
| 62 | 70 | void selectionEnded(Rectangle r, MouseEvent e); |
| 63 | 71 | |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 74 | 82 | */ |
| 75 | 83 | void removePropertyChangeListener(PropertyChangeListener listener); |
| 76 | 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 | |
| 77 | 110 | /** |
| 78 | 111 | * The listener that receives the events after left mouse button is released. |
| 79 | 112 | */ |
| 80 | 113 | private final SelectionEnded selectionEndedListener; |
| 81 | 114 | /** |
| 82 | 115 | * Position of the map when the mouse button was pressed. |
| 83 | | * If this is not <code>null</code>, a rectangle is drawn on screen. |
| | 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. |
| 84 | 118 | */ |
| 85 | 119 | private Point mousePosStart; |
| 86 | 120 | /** |
| 87 | | * Position of the map when the selection rectangle was last drawn. |
| | 121 | * The last position of the mouse while the mouse button was pressed. |
| 88 | 122 | */ |
| 89 | 123 | private Point mousePos; |
| 90 | 124 | /** |
| 91 | | * The Component, the selection rectangle is drawn onto. |
| | 125 | * The Component that provides us with OSM data and the aspect is taken from. |
| 92 | 126 | */ |
| 93 | 127 | private final NavigatableComponent nc; |
| 94 | 128 | /** |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 97 | 131 | */ |
| 98 | 132 | private boolean aspectRatio; |
| 99 | 133 | |
| | 134 | /** |
| | 135 | * <code>true</code> if we should paint a lasso instead of a rectangle. |
| | 136 | */ |
| 100 | 137 | private boolean lassoMode; |
| | 138 | /** |
| | 139 | * The polygon to store the selection outline if {@link #lassoMode} is used. |
| | 140 | */ |
| 101 | 141 | private Polygon lasso = new Polygon(); |
| 102 | 142 | |
| 103 | 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 | /** |
| 104 | 151 | * Create a new SelectionManager. |
| 105 | 152 | * |
| 106 | 153 | * @param selectionEndedListener The action listener that receives the event when |
| 107 | 154 | * the left button is released. |
| 108 | 155 | * @param aspectRatio If true, the selection window must obtain the aspect |
| 109 | 156 | * ratio of the drawComponent. |
| 110 | | * @param navComp The component, the rectangle is drawn onto. |
| | 157 | * @param navComp The component that provides us with OSM data and the aspect is taken from. |
| 111 | 158 | */ |
| 112 | 159 | public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) { |
| 113 | 160 | this.selectionEndedListener = selectionEndedListener; |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 116 | 163 | } |
| 117 | 164 | |
| 118 | 165 | /** |
| 119 | | * Register itself at the given event source. |
| | 166 | * Register itself at the given event source and add a hint layer. |
| 120 | 167 | * @param eventSource The emitter of the mouse events. |
| 121 | 168 | * @param lassoMode {@code true} to enable lasso mode, {@code false} to disable it. |
| 122 | 169 | */ |
| 123 | | public void register(NavigatableComponent eventSource, boolean lassoMode) { |
| | 170 | public void register(MapView eventSource, boolean lassoMode) { |
| 124 | 171 | this.lassoMode = lassoMode; |
| 125 | 172 | eventSource.addMouseListener(this); |
| 126 | 173 | eventSource.addMouseMotionListener(this); |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 128 | 175 | eventSource.addPropertyChangeListener("scale", new PropertyChangeListener() { |
| 129 | 176 | @Override |
| 130 | 177 | public void propertyChange(PropertyChangeEvent evt) { |
| 131 | | if (mousePosStart != null) { |
| 132 | | paintRect(); |
| 133 | | mousePos = mousePosStart = null; |
| 134 | | } |
| | 178 | abortSelecting(); |
| 135 | 179 | } |
| 136 | 180 | }); |
| | 181 | eventSource.addTemporaryLayer(selectionHintLayer); |
| 137 | 182 | } |
| 138 | 183 | /** |
| 139 | | * Unregister itself from the given event source. If a selection rectangle is |
| 140 | | * shown, hide it first. |
| | 184 | * Unregister itself from the given event source and hide the selection hint layer. |
| 141 | 185 | * |
| 142 | 186 | * @param eventSource The emitter of the mouse events. |
| 143 | 187 | */ |
| 144 | | public void unregister(Component eventSource) { |
| | 188 | public void unregister(MapView eventSource) { |
| | 189 | abortSelecting(); |
| | 190 | eventSource.removeTemporaryLayer(selectionHintLayer); |
| 145 | 191 | eventSource.removeMouseListener(this); |
| 146 | 192 | eventSource.removeMouseMotionListener(this); |
| 147 | 193 | selectionEndedListener.removePropertyChangeListener(this); |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 175 | 221 | if (mousePosStart == null) { |
| 176 | 222 | mousePosStart = mousePos = e.getPoint(); |
| 177 | 223 | } |
| 178 | | if (!lassoMode) { |
| 179 | | paintRect(); |
| 180 | | } |
| | 224 | selectionAreaChanged(); |
| 181 | 225 | } |
| 182 | 226 | |
| 183 | 227 | if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) { |
| 184 | 228 | mousePos = e.getPoint(); |
| 185 | | if (lassoMode) { |
| 186 | | paintLasso(); |
| 187 | | } else { |
| 188 | | paintRect(); |
| 189 | | } |
| | 229 | addLassoPoint(e.getPoint()); |
| | 230 | selectionAreaChanged(); |
| 190 | 231 | } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) { |
| 191 | | mousePosStart.x += e.getX()-mousePos.x; |
| 192 | | mousePosStart.y += e.getY()-mousePos.y; |
| | 232 | moveSelection(e.getX()-mousePos.x, e.getY()-mousePos.y); |
| 193 | 233 | mousePos = e.getPoint(); |
| 194 | | paintRect(); |
| | 234 | selectionAreaChanged(); |
| 195 | 235 | } |
| 196 | 236 | } |
| 197 | 237 | |
| 198 | 238 | /** |
| | 239 | * Moves the current selection by some pixels. |
| | 240 | * @param dx How much to move it in x direction. |
| | 241 | * @param dy How much to move it in y direction. |
| | 242 | */ |
| | 243 | private void moveSelection(int dx, int dy) { |
| | 244 | mousePosStart.x += dx; |
| | 245 | mousePosStart.y += dy; |
| | 246 | lasso.translate(dx, dy); |
| | 247 | } |
| | 248 | |
| | 249 | /** |
| 199 | 250 | * Check the state of the keys and buttons and set the selection accordingly. |
| 200 | 251 | */ |
| 201 | 252 | @Override |
| 202 | 253 | public void mouseReleased(MouseEvent e) { |
| 203 | | if (e.getButton() != MouseEvent.BUTTON1) |
| 204 | | return; |
| 205 | | if (mousePos == null || mousePosStart == null) |
| 206 | | return; // injected release from outside |
| 207 | | // disable the selection rect |
| 208 | | Rectangle r; |
| 209 | | if (!lassoMode) { |
| 210 | | nc.requestClearRect(); |
| 211 | | r = getSelectionRectangle(); |
| 212 | | |
| 213 | | lasso = rectToPolygon(r); |
| 214 | | } else { |
| 215 | | nc.requestClearPoly(); |
| 216 | | lasso.addPoint(mousePos.x, mousePos.y); |
| 217 | | r = lasso.getBounds(); |
| 218 | | } |
| 219 | | mousePosStart = null; |
| 220 | | mousePos = null; |
| 221 | | |
| 222 | | if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0) { |
| 223 | | selectionEndedListener.selectionEnded(r, e); |
| | 254 | if (e.getButton() == MouseEvent.BUTTON1) { |
| | 255 | endSelecting(e); |
| 224 | 256 | } |
| 225 | 257 | } |
| 226 | 258 | |
| 227 | 259 | /** |
| 228 | | * Draws a selection rectangle on screen. |
| | 260 | * Ends the selection of the current area. This simulates a release of mouse button 1. |
| | 261 | * @param e A mouse event that caused this. Needed for backward compatibility. |
| 229 | 262 | */ |
| 230 | | private void paintRect() { |
| 231 | | if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) |
| 232 | | return; |
| 233 | | nc.requestPaintRect(getSelectionRectangle()); |
| | 263 | public void endSelecting(MouseEvent e) { |
| | 264 | mousePos = e.getPoint(); |
| | 265 | if (lassoMode) { |
| | 266 | addLassoPoint(e.getPoint()); |
| | 267 | } |
| | 268 | |
| | 269 | // Left mouse was released while right is still pressed. |
| | 270 | boolean rightMouseStillPressed = (e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0; |
| | 271 | |
| | 272 | if (!rightMouseStillPressed) { |
| | 273 | selectingDone(e); |
| | 274 | } |
| | 275 | abortSelecting(); |
| 234 | 276 | } |
| 235 | 277 | |
| 236 | | private void paintLasso() { |
| 237 | | if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) { |
| | 278 | private void addLassoPoint(Point point) { |
| | 279 | if (isNoSelection()) { |
| 238 | 280 | return; |
| 239 | 281 | } |
| 240 | | lasso.addPoint(mousePos.x, mousePos.y); |
| 241 | | nc.requestPaintPoly(lasso); |
| | 282 | lasso.addPoint(point.x, point.y); |
| | 283 | } |
| | 284 | |
| | 285 | private boolean isNoSelection() { |
| | 286 | return mousePos == null || mousePosStart == null || mousePos == mousePosStart; |
| 242 | 287 | } |
| 243 | 288 | |
| 244 | 289 | /** |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 286 | 331 | */ |
| 287 | 332 | @Override |
| 288 | 333 | public void propertyChange(PropertyChangeEvent evt) { |
| 289 | | if ("active".equals(evt.getPropertyName()) && !(Boolean) evt.getNewValue() && mousePosStart != null) { |
| 290 | | paintRect(); |
| 291 | | mousePosStart = null; |
| 292 | | mousePos = null; |
| | 334 | if ("active".equals(evt.getPropertyName()) && !(Boolean)evt.getNewValue()) { |
| | 335 | abortSelecting(); |
| 293 | 336 | } |
| 294 | 337 | } |
| 295 | 338 | |
| 296 | 339 | /** |
| 297 | | * Return a list of all objects in the selection, respecting the different |
| | 340 | * Stores the current selection and stores the result in {@link #selectionResult} to be retrieved by {@link #getSelectedObjects(boolean)} later. |
| | 341 | * @param e The mouse event that caused the selection to be finished. |
| | 342 | */ |
| | 343 | private void selectingDone(MouseEvent e) { |
| | 344 | if (isNoSelection()) { |
| | 345 | // Nothing selected. |
| | 346 | return; |
| | 347 | } |
| | 348 | Rectangle r; |
| | 349 | if (lassoMode) { |
| | 350 | r = lasso.getBounds(); |
| | 351 | |
| | 352 | selectionResult = new Polygon(lasso.xpoints, lasso.ypoints, lasso.npoints); |
| | 353 | } else { |
| | 354 | r = getSelectionRectangle(); |
| | 355 | |
| | 356 | selectionResult = rectToPolygon(r); |
| | 357 | } |
| | 358 | selectionEndedListener.selectionEnded(r, e); |
| | 359 | } |
| | 360 | |
| | 361 | private void abortSelecting() { |
| | 362 | if (mousePosStart != null) { |
| | 363 | mousePos = mousePosStart = null; |
| | 364 | lasso.reset(); |
| | 365 | selectionAreaChanged(); |
| | 366 | } |
| | 367 | } |
| | 368 | |
| | 369 | private void selectionAreaChanged() { |
| | 370 | // Trigger a redraw of the map view. |
| | 371 | // A nicer way would be to provide change events for the temporary layer. |
| | 372 | Main.map.mapView.repaint(); |
| | 373 | } |
| | 374 | |
| | 375 | /** |
| | 376 | * Return a list of all objects in the active/last selection, respecting the different |
| 298 | 377 | * modifier. |
| 299 | 378 | * |
| 300 | 379 | * @param alt Whether the alt key was pressed, which means select all |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 307 | 386 | |
| 308 | 387 | // whether user only clicked, not dragged. |
| 309 | 388 | boolean clicked = false; |
| 310 | | Rectangle bounding = lasso.getBounds(); |
| | 389 | Rectangle bounding = selectionResult.getBounds(); |
| 311 | 390 | if (bounding.height <= 2 && bounding.width <= 2) { |
| 312 | 391 | clicked = true; |
| 313 | 392 | } |
| 314 | 393 | |
| 315 | 394 | if (clicked) { |
| 316 | | Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]); |
| | 395 | Point center = new Point(selectionResult.xpoints[0], selectionResult.ypoints[0]); |
| 317 | 396 | OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false); |
| 318 | 397 | if (osm != null) { |
| 319 | 398 | selection.add(osm); |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 321 | 400 | } else { |
| 322 | 401 | // nodes |
| 323 | 402 | for (Node n : nc.getCurrentDataSet().getNodes()) { |
| 324 | | if (n.isSelectable() && lasso.contains(nc.getPoint2D(n))) { |
| | 403 | if (n.isSelectable() && selectionResult.contains(nc.getPoint2D(n))) { |
| 325 | 404 | selection.add(n); |
| 326 | 405 | } |
| 327 | 406 | } |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 333 | 412 | } |
| 334 | 413 | if (alt) { |
| 335 | 414 | for (Node n : w.getNodes()) { |
| 336 | | if (!n.isIncomplete() && lasso.contains(nc.getPoint2D(n))) { |
| | 415 | if (!n.isIncomplete() && selectionResult.contains(nc.getPoint2D(n))) { |
| 337 | 416 | selection.add(w); |
| 338 | 417 | break; |
| 339 | 418 | } |
| … |
… |
public class SelectionManager implements MouseListener, MouseMotionListener, Pro
|
| 341 | 420 | } else { |
| 342 | 421 | boolean allIn = true; |
| 343 | 422 | for (Node n : w.getNodes()) { |
| 344 | | if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) { |
| | 423 | if (!n.isIncomplete() && !selectionResult.contains(nc.getPoint(n))) { |
| 345 | 424 | allIn = false; |
| 346 | 425 | break; |
| 347 | 426 | } |