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

Last change on this file since 5424 was 5152, checked in by simon04, 12 years ago

see #3910 - original patch by Oliver Raupach - add lasso selection mode

  • Property svn:eol-style set to native
File size: 12.8 KB
RevLine 
[298]1// License: GPL. Copyright 2007 by Immanuel Scholz and others
[4]2package org.openstreetmap.josm.gui;
[3]3
4import java.awt.Color;
5import java.awt.Component;
6import java.awt.Graphics;
7import java.awt.Point;
[5152]8import java.awt.Polygon;
[3]9import java.awt.Rectangle;
10import java.awt.event.InputEvent;
11import java.awt.event.MouseEvent;
12import java.awt.event.MouseListener;
13import java.awt.event.MouseMotionListener;
[7]14import java.beans.PropertyChangeEvent;
15import java.beans.PropertyChangeListener;
16import java.util.Collection;
17import java.util.LinkedList;
[3]18
[7]19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
[64]21import org.openstreetmap.josm.data.osm.Way;
[7]22
[3]23/**
24 * Manages the selection of a rectangle. Listening to left and right mouse button
25 * presses and to mouse motions and draw the rectangle accordingly.
[1169]26 *
[3]27 * 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
29 * around. Releasing the left button fires an action event to the listener given
30 * at constructor, except if the right is still pressed, which just remove the
31 * selection rectangle and does nothing.
[1169]32 *
33 * The point where the left mouse button was pressed and the current mouse
[3]34 * position are two opposite corners of the selection rectangle.
[1169]35 *
36 * It is possible to specify an aspect ratio (width per height) which the
[3]37 * selection rectangle always must have. In this case, the selection rectangle
38 * will be the largest window with this aspect ratio, where the position the left
[1169]39 * mouse button was pressed and the corner of the current mouse position are at
[3]40 * opposite sites (the mouse position corner is the corner nearest to the mouse
[1169]41 * cursor).
42 *
43 * When the left mouse button was released, an ActionEvent is send to the
[3]44 * ActionListener given at constructor. The source of this event is this manager.
[1169]45 *
[3]46 * @author imi
47 */
[7]48public class SelectionManager implements MouseListener, MouseMotionListener, PropertyChangeListener {
[3]49
[1169]50 /**
51 * This is the interface that an user of SelectionManager has to implement
52 * to get informed when a selection closes.
53 * @author imi
54 */
55 public interface SelectionEnded {
56 /**
57 * Called, when the left mouse button was released.
58 * @param r The rectangle that is currently the selection.
59 * @param alt Whether the alt key was pressed
60 * @param shift Whether the shift key was pressed
61 * @param ctrl Whether the ctrl key was pressed
62 * @see InputEvent#getModifiersEx()
63 */
[3594]64 public void selectionEnded(Rectangle r, MouseEvent e);
[1169]65 /**
66 * Called to register the selection manager for "active" property.
67 * @param listener The listener to register
68 */
69 public void addPropertyChangeListener(PropertyChangeListener listener);
70 /**
71 * Called to remove the selection manager from the listener list
72 * for "active" property.
73 * @param listener The listener to register
74 */
75 public void removePropertyChangeListener(PropertyChangeListener listener);
76 }
77 /**
78 * The listener that receives the events after left mouse button is released.
79 */
80 private final SelectionEnded selectionEndedListener;
81 /**
82 * Position of the map when the mouse button was pressed.
83 * If this is not <code>null</code>, a rectangle is drawn on screen.
84 */
85 private Point mousePosStart;
86 /**
87 * Position of the map when the selection rectangle was last drawn.
88 */
89 private Point mousePos;
90 /**
91 * The Component, the selection rectangle is drawn onto.
92 */
93 private final NavigatableComponent nc;
94 /**
95 * Whether the selection rectangle must obtain the aspect ratio of the
96 * drawComponent.
97 */
98 private boolean aspectRatio;
[3]99
[5152]100 private boolean lassoMode;
101 private Polygon lasso = new Polygon();
102
[1169]103 /**
104 * Create a new SelectionManager.
105 *
106 * @param selectionEndedListener The action listener that receives the event when
107 * the left button is released.
108 * @param aspectRatio If true, the selection window must obtain the aspect
109 * ratio of the drawComponent.
110 * @param navComp The component, the rectangle is drawn onto.
111 */
112 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) {
113 this.selectionEndedListener = selectionEndedListener;
114 this.aspectRatio = aspectRatio;
115 this.nc = navComp;
116 }
117
118 /**
119 * Register itself at the given event source.
120 * @param eventSource The emitter of the mouse events.
121 */
[5152]122 public void register(NavigatableComponent eventSource, boolean lassoMode) {
123 this.lassoMode = lassoMode;
[1169]124 eventSource.addMouseListener(this);
125 eventSource.addMouseMotionListener(this);
126 selectionEndedListener.addPropertyChangeListener(this);
[100]127 eventSource.addPropertyChangeListener("scale", new PropertyChangeListener(){
[1169]128 public void propertyChange(PropertyChangeEvent evt) {
129 if (mousePosStart != null) {
130 paintRect();
131 mousePos = mousePosStart = null;
132 }
[100]133 }
134 });
[1169]135 }
136 /**
137 * Unregister itself from the given event source. If a selection rectangle is
138 * shown, hide it first.
139 *
140 * @param eventSource The emitter of the mouse events.
141 */
142 public void unregister(Component eventSource) {
143 eventSource.removeMouseListener(this);
144 eventSource.removeMouseMotionListener(this);
145 selectionEndedListener.removePropertyChangeListener(this);
146 }
[3]147
[1169]148 /**
149 * If the correct button, from the "drawing rectangle" mode
150 */
151 public void mousePressed(MouseEvent e) {
[1911]152 if (e.getButton() == MouseEvent.BUTTON1) {
[1169]153 mousePosStart = mousePos = e.getPoint();
[5152]154
155 lasso.reset();
156 lasso.addPoint(mousePosStart.x, mousePosStart.y);
[1911]157 }
[1169]158 }
[3]159
[1169]160 /**
161 * If the correct button is hold, draw the rectangle.
162 */
163 public void mouseDragged(MouseEvent e) {
164 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
[3]165
[1169]166 if (buttonPressed != 0) {
[1911]167 if (mousePosStart == null) {
[1169]168 mousePosStart = mousePos = e.getPoint();
[1911]169 }
[5152]170 if (!lassoMode) {
171 paintRect();
172 }
[1169]173 }
[7]174
[1169]175 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
176 mousePos = e.getPoint();
[5152]177 if (lassoMode) {
178 paintLasso();
179 } else {
180 paintRect();
181 }
[1169]182 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
183 mousePosStart.x += e.getX()-mousePos.x;
184 mousePosStart.y += e.getY()-mousePos.y;
185 mousePos = e.getPoint();
186 paintRect();
187 }
188 }
[3]189
[1169]190 /**
191 * Check the state of the keys and buttons and set the selection accordingly.
192 */
193 public void mouseReleased(MouseEvent e) {
194 if (e.getButton() != MouseEvent.BUTTON1)
195 return;
196 if (mousePos == null || mousePosStart == null)
197 return; // injected release from outside
[5152]198 // disable the selection rect
199 Rectangle r;
200 if (!lassoMode) {
201 paintRect();
202 r = getSelectionRectangle();
[3]203
[5152]204 lasso = rectToPolygon(r);
205 } else {
206 lasso.addPoint(mousePos.x, mousePos.y);
207 r = lasso.getBounds();
208 }
[1169]209 mousePosStart = null;
210 mousePos = null;
[3]211
[1911]212 if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0) {
[3594]213 selectionEndedListener.selectionEnded(r, e);
[1911]214 }
[1169]215 }
[3]216
[1169]217 /**
218 * Draw a selection rectangle on screen. If already a rectangle is drawn,
219 * it is removed instead.
220 */
221 private void paintRect() {
222 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
223 return;
224 Graphics g = nc.getGraphics();
225 g.setColor(Color.BLACK);
226 g.setXORMode(Color.WHITE);
[7]227
[1169]228 Rectangle r = getSelectionRectangle();
229 g.drawRect(r.x,r.y,r.width,r.height);
230 }
[7]231
[5152]232 private void paintLasso() {
233 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) {
234 return;
235 }
236
237 Graphics g = nc.getGraphics();
238 g.setColor(Color.WHITE);
239
240 int lastPosX = lasso.xpoints[lasso.npoints - 1];
241 int lastPosY = lasso.ypoints[lasso.npoints - 1];
242 g.drawLine(lastPosX, lastPosY, mousePos.x, mousePos.y);
243
244 lasso.addPoint(mousePos.x, mousePos.y);
245 }
246
[1169]247 /**
248 * Calculate and return the current selection rectangle
249 * @return A rectangle that spans from mousePos to mouseStartPos
250 */
251 private Rectangle getSelectionRectangle() {
252 int x = mousePosStart.x;
253 int y = mousePosStart.y;
254 int w = mousePos.x - mousePosStart.x;
255 int h = mousePos.y - mousePosStart.y;
256 if (w < 0) {
257 x += w;
258 w = -w;
259 }
260 if (h < 0) {
261 y += h;
262 h = -h;
263 }
[7]264
[1169]265 if (aspectRatio) {
266 /* Keep the aspect ratio by growing the rectangle; the
267 * rectangle is always under the cursor. */
268 double aspectRatio = (double)nc.getWidth()/nc.getHeight();
269 if ((double)w/h < aspectRatio) {
270 int neww = (int)(h*aspectRatio);
[1911]271 if (mousePos.x < mousePosStart.x) {
[1169]272 x += w - neww;
[1911]273 }
[1169]274 w = neww;
275 } else {
276 int newh = (int)(w/aspectRatio);
[1911]277 if (mousePos.y < mousePosStart.y) {
[1169]278 y += h - newh;
[1911]279 }
[1169]280 h = newh;
281 }
282 }
283
284 return new Rectangle(x,y,w,h);
285 }
286
287 /**
288 * If the action goes inactive, remove the selection rectangle from screen
289 */
290 public void propertyChange(PropertyChangeEvent evt) {
291 if (evt.getPropertyName().equals("active") && !(Boolean)evt.getNewValue() && mousePosStart != null) {
292 paintRect();
293 mousePosStart = null;
294 mousePos = null;
295 }
296 }
297
298 /**
[5152]299 * Return a list of all objects in the selection, respecting the different
[1169]300 * modifier.
[5152]301 *
302 * @param alt Whether the alt key was pressed, which means select all
303 * objects that are touched, instead those which are completely covered.
[1169]304 */
[5152]305 public Collection<OsmPrimitive> getSelectedObjects(boolean alt) {
306
[1169]307 Collection<OsmPrimitive> selection = new LinkedList<OsmPrimitive>();
308
309 // whether user only clicked, not dragged.
[5152]310 boolean clicked = false;
311 Rectangle bounding = lasso.getBounds();
312 if (bounding.height <= 2 && bounding.width <= 2) {
313 clicked = true;
314 }
[1169]315
316 if (clicked) {
[5152]317 Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]);
[3642]318 OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false);
[1911]319 if (osm != null) {
[1169]320 selection.add(osm);
[1911]321 }
[1169]322 } else {
323 // nodes
[2381]324 for (Node n : nc.getCurrentDataSet().getNodes()) {
[5152]325 if (n.isSelectable() && lasso.contains(nc.getPoint(n))) {
[1169]326 selection.add(n);
[1911]327 }
[1169]328 }
329
330 // ways
[2381]331 for (Way w : nc.getCurrentDataSet().getWays()) {
[3198]332 if (!w.isSelectable() || w.getNodesCount() == 0) {
[1911]333 continue;
334 }
[1169]335 if (alt) {
[1910]336 for (Node n : w.getNodes()) {
[5152]337 if (!n.isIncomplete() && lasso.contains(nc.getPoint(n))) {
[1169]338 selection.add(w);
339 break;
340 }
341 }
342 } else {
343 boolean allIn = true;
[1910]344 for (Node n : w.getNodes()) {
[5152]345 if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) {
[1169]346 allIn = false;
347 break;
348 }
349 }
[1911]350 if (allIn) {
351 selection.add(w);
352 }
[1169]353 }
354 }
355 }
356 return selection;
357 }
358
[5152]359 private Polygon rectToPolygon(Rectangle r) {
360 Polygon poly = new Polygon();
361
362 poly.addPoint(r.x, r.y);
363 poly.addPoint(r.x, r.y + r.height);
364 poly.addPoint(r.x + r.width, r.y + r.height);
365 poly.addPoint(r.x + r.width, r.y);
366
367 return poly;
368 }
369
370 public void setLassoMode(boolean lassoMode) {
371 this.lassoMode = lassoMode;
372 }
373
[1169]374 public void mouseClicked(MouseEvent e) {}
375 public void mouseEntered(MouseEvent e) {}
376 public void mouseExited(MouseEvent e) {}
377 public void mouseMoved(MouseEvent e) {}
[3]378}
Note: See TracBrowser for help on using the repository browser.