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

Last change on this file since 14483 was 13434, checked in by Don-vip, 6 years ago

see #8039, see #10456 - support read-only data layers

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