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

Last change on this file since 3351 was 3198, checked in by bastiK, 14 years ago

see #4929 - rectangle selection selects filtered objects.
(the other things mentioned in this ticket are not addressed)

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