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

Last change on this file since 16 was 16, checked in by imi, 19 years ago
  • reverted to 14, but kept the global Projection.
  • improved the preference settings for projections.
File size: 10.5 KB
Line 
1package org.openstreetmap.josm.gui;
2
3import java.awt.Color;
4import java.awt.Component;
5import java.awt.Graphics;
6import java.awt.Point;
7import java.awt.Rectangle;
8import java.awt.event.InputEvent;
9import java.awt.event.MouseEvent;
10import java.awt.event.MouseListener;
11import java.awt.event.MouseMotionListener;
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.Collection;
15import java.util.LinkedList;
16
17import org.openstreetmap.josm.data.osm.LineSegment;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.Track;
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 MapView, the selection rectangle is drawn onto.
91 */
92 private final MapView mv;
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 actionListener 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 mapView The view, the rectangle is drawn onto.
107 */
108 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, MapView mapView) {
109 this.selectionEndedListener = selectionEndedListener;
110 this.aspectRatio = aspectRatio;
111 this.mv = mapView;
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(Component eventSource) {
119 eventSource.addMouseListener(this);
120 eventSource.addMouseMotionListener(this);
121 selectionEndedListener.addPropertyChangeListener(this);
122 }
123 /**
124 * Unregister itself from the given event source. If a selection rectangle is
125 * shown, hide it first.
126 *
127 * @param eventSource The emitter of the mouse events.
128 */
129 public void unregister(Component eventSource) {
130 eventSource.removeMouseListener(this);
131 eventSource.removeMouseMotionListener(this);
132 selectionEndedListener.removePropertyChangeListener(this);
133 }
134
135 /**
136 * If the correct button, start the "drawing rectangle" mode
137 */
138 public void mousePressed(MouseEvent e) {
139 if (e.getButton() == MouseEvent.BUTTON1)
140 mousePosStart = mousePos = e.getPoint();
141 }
142
143 /**
144 * If the correct button is hold, draw the rectangle.
145 */
146 public void mouseDragged(MouseEvent e) {
147 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
148
149
150 if (buttonPressed != 0) {
151 if (mousePosStart == null)
152 mousePosStart = mousePos = e.getPoint();
153 paintRect();
154 }
155
156 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
157 mousePos = e.getPoint();
158 paintRect();
159 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
160 mousePosStart.x += e.getX()-mousePos.x;
161 mousePosStart.y += e.getY()-mousePos.y;
162 mousePos = e.getPoint();
163 paintRect();
164 }
165 }
166
167 /**
168 * Check the state of the keys and buttons and set the selection accordingly.
169 */
170 public void mouseReleased(MouseEvent e) {
171 if (e.getButton() != MouseEvent.BUTTON1)
172 return;
173 if (mousePos == null || mousePosStart == null)
174 return; // injected release from outside
175
176 // disable the selection rect
177 paintRect();
178 Rectangle r = getSelectionRectangle();
179 mousePosStart = null;
180 mousePos = null;
181
182 boolean shift = (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0;
183 boolean alt = (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0;
184 boolean ctrl = (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0;
185 if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0)
186 selectionEndedListener.selectionEnded(r, alt, shift, ctrl);
187 }
188
189
190 /**
191 * Draw a selection rectangle on screen. If already a rectangle is drawn,
192 * it is removed instead.
193 */
194 private void paintRect() {
195 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
196 return;
197 Graphics g = mv.getGraphics();
198 g.setColor(Color.BLACK);
199 g.setXORMode(Color.WHITE);
200
201 Rectangle r = getSelectionRectangle();
202 g.drawRect(r.x,r.y,r.width,r.height);
203 }
204
205 /**
206 * Calculate and return the current selection rectangle
207 * @return A rectangle that spans from mousePos to mouseStartPos
208 */
209 private Rectangle getSelectionRectangle() {
210 int x = mousePosStart.x;
211 int y = mousePosStart.y;
212 int w = mousePos.x - mousePosStart.x;
213 int h = mousePos.y - mousePosStart.y;
214 if (w < 0) {
215 x += w;
216 w = -w;
217 }
218 if (h < 0) {
219 y += h;
220 h = -h;
221 }
222
223 if (aspectRatio) {
224 // keep the aspect ration by shrinking the rectangle
225 double aspectRatio = (double)mv.getWidth()/mv.getHeight();
226 if ((double)w/h > aspectRatio) {
227 int neww = (int)(h*aspectRatio);
228 if (mousePos.x < mousePosStart.x)
229 x += w-neww;
230 w = neww;
231 } else {
232 int newh = (int)(w/aspectRatio);
233 if (mousePos.y < mousePosStart.y)
234 y += h-newh;
235 h = newh;
236 }
237 }
238
239 return new Rectangle(x,y,w,h);
240 }
241
242 /**
243 * If the action goes inactive, remove the selection rectangle from screen
244 */
245 public void propertyChange(PropertyChangeEvent evt) {
246 if (evt.getPropertyName().equals("active") && !(Boolean)evt.getNewValue() && mousePosStart != null) {
247 paintRect();
248 mousePosStart = null;
249 mousePos = null;
250 }
251 }
252
253 /**
254 * Return a list of all objects in the rectangle, respecting the different
255 * modifier.
256 * @param alt Whether the alt key was pressed, which means select all objects
257 * that are touched, instead those which are completly covered. Also
258 * select whole tracks instead of line segments.
259 */
260 public Collection<OsmPrimitive> getObjectsInRectangle(Rectangle r, boolean alt) {
261 Collection<OsmPrimitive> selection = new LinkedList<OsmPrimitive>();
262
263 // whether user only clicked, not dragged.
264 boolean clicked = r.width <= 2 && r.height <= 2;
265 Point center = new Point(r.x+r.width/2, r.y+r.height/2);
266
267 if (clicked) {
268 OsmPrimitive osm = mv.getNearest(center, alt);
269 if (osm != null)
270 selection.add(osm);
271 } else {
272 // nodes
273 for (Node n : mv.dataSet.nodes) {
274 if (r.contains(mv.getScreenPoint(n.coor)))
275 selection.add(n);
276 }
277
278 // pending line segments
279 for (LineSegment ls : mv.dataSet.pendingLineSegments())
280 if (rectangleContainLineSegment(r, alt, ls))
281 selection.add(ls);
282
283 // tracks
284 for (Track t : mv.dataSet.tracks()) {
285 boolean wholeTrackSelected = !t.segments().isEmpty();
286 for (LineSegment ls : t.segments())
287 if (rectangleContainLineSegment(r, alt, ls))
288 selection.add(ls);
289 else
290 wholeTrackSelected = false;
291 if (wholeTrackSelected)
292 selection.add(t);
293 }
294
295 // TODO areas
296 }
297 return selection;
298 }
299
300 /**
301 * Decide whether the line segment is in the rectangle Return
302 * <code>true</code>, if it is in or false if not.
303 *
304 * @param r The rectangle, in which the line segment has to be.
305 * @param alt Whether user pressed the Alt key
306 * @param ls The line segment.
307 * @return <code>true</code>, if the LineSegment was added to the selection.
308 */
309 private boolean rectangleContainLineSegment(Rectangle r, boolean alt, LineSegment ls) {
310 if (alt) {
311 Point p1 = mv.getScreenPoint(ls.getStart().coor);
312 Point p2 = mv.getScreenPoint(ls.getEnd().coor);
313 if (r.intersectsLine(p1.x, p1.y, p2.x, p2.y))
314 return true;
315 } else {
316 if (r.contains(mv.getScreenPoint(ls.getStart().coor))
317 && r.contains(mv.getScreenPoint(ls.getEnd().coor)))
318 return true;
319 }
320 return false;
321 }
322
323
324 /**
325 * Does nothing. Only to satisfy MouseListener
326 */
327 public void mouseClicked(MouseEvent e) {}
328 /**
329 * Does nothing. Only to satisfy MouseListener
330 */
331 public void mouseEntered(MouseEvent e) {}
332 /**
333 * Does nothing. Only to satisfy MouseListener
334 */
335 public void mouseExited(MouseEvent e) {}
336 /**
337 * Does nothing. Only to satisfy MouseMotionListener
338 */
339 public void mouseMoved(MouseEvent e) {}
340
341}
Note: See TracBrowser for help on using the repository browser.