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

Last change on this file since 22 was 22, checked in by imi, 18 years ago

starting restructure of dataset. Checkpoint is broken!

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.Main;
18import org.openstreetmap.josm.data.osm.LineSegment;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Track;
22
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.
26 *
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.
32 *
33 * The point where the left mouse button was pressed and the current mouse
34 * position are two opposite corners of the selection rectangle.
35 *
36 * It is possible to specify an aspect ratio (width per height) which the
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
39 * mouse button was pressed and the corner of the current mouse position are at
40 * opposite sites (the mouse position corner is the corner nearest to the mouse
41 * cursor).
42 *
43 * When the left mouse button was released, an ActionEvent is send to the
44 * ActionListener given at constructor. The source of this event is this manager.
45 *
46 * @author imi
47 */
48public class SelectionManager implements MouseListener, MouseMotionListener, PropertyChangeListener {
49
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 */
64 public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl);
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 MapView, the selection rectangle is drawn onto.
92 */
93 private final MapView mv;
94 /**
95 * Whether the selection rectangle must obtain the aspect ratio of the
96 * drawComponent.
97 */
98 private boolean aspectRatio;
99
100 /**
101 * Create a new SelectionManager.
102 *
103 * @param actionListener The action listener that receives the event when
104 * the left button is released.
105 * @param aspectRatio If true, the selection window must obtain the aspect
106 * ratio of the drawComponent.
107 * @param mapView The view, the rectangle is drawn onto.
108 */
109 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, MapView mapView) {
110 this.selectionEndedListener = selectionEndedListener;
111 this.aspectRatio = aspectRatio;
112 this.mv = mapView;
113 }
114
115 /**
116 * Register itself at the given event source.
117 * @param eventSource The emitter of the mouse events.
118 */
119 public void register(Component eventSource) {
120 eventSource.addMouseListener(this);
121 eventSource.addMouseMotionListener(this);
122 selectionEndedListener.addPropertyChangeListener(this);
123 }
124 /**
125 * Unregister itself from the given event source. If a selection rectangle is
126 * shown, hide it first.
127 *
128 * @param eventSource The emitter of the mouse events.
129 */
130 public void unregister(Component eventSource) {
131 eventSource.removeMouseListener(this);
132 eventSource.removeMouseMotionListener(this);
133 selectionEndedListener.removePropertyChangeListener(this);
134 }
135
136 /**
137 * If the correct button, start the "drawing rectangle" mode
138 */
139 public void mousePressed(MouseEvent e) {
140 if (e.getButton() == MouseEvent.BUTTON1)
141 mousePosStart = mousePos = e.getPoint();
142 }
143
144 /**
145 * If the correct button is hold, draw the rectangle.
146 */
147 public void mouseDragged(MouseEvent e) {
148 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
149
150
151 if (buttonPressed != 0) {
152 if (mousePosStart == null)
153 mousePosStart = mousePos = e.getPoint();
154 paintRect();
155 }
156
157 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
158 mousePos = e.getPoint();
159 paintRect();
160 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
161 mousePosStart.x += e.getX()-mousePos.x;
162 mousePosStart.y += e.getY()-mousePos.y;
163 mousePos = e.getPoint();
164 paintRect();
165 }
166 }
167
168 /**
169 * Check the state of the keys and buttons and set the selection accordingly.
170 */
171 public void mouseReleased(MouseEvent e) {
172 if (e.getButton() != MouseEvent.BUTTON1)
173 return;
174 if (mousePos == null || mousePosStart == null)
175 return; // injected release from outside
176
177 // disable the selection rect
178 paintRect();
179 Rectangle r = getSelectionRectangle();
180 mousePosStart = null;
181 mousePos = null;
182
183 boolean shift = (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0;
184 boolean alt = (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0;
185 boolean ctrl = (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0;
186 if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0)
187 selectionEndedListener.selectionEnded(r, alt, shift, ctrl);
188 }
189
190
191 /**
192 * Draw a selection rectangle on screen. If already a rectangle is drawn,
193 * it is removed instead.
194 */
195 private void paintRect() {
196 if (mousePos == null || mousePosStart == null || mousePos == mousePosStart)
197 return;
198 Graphics g = mv.getGraphics();
199 g.setColor(Color.BLACK);
200 g.setXORMode(Color.WHITE);
201
202 Rectangle r = getSelectionRectangle();
203 g.drawRect(r.x,r.y,r.width,r.height);
204 }
205
206 /**
207 * Calculate and return the current selection rectangle
208 * @return A rectangle that spans from mousePos to mouseStartPos
209 */
210 private Rectangle getSelectionRectangle() {
211 int x = mousePosStart.x;
212 int y = mousePosStart.y;
213 int w = mousePos.x - mousePosStart.x;
214 int h = mousePos.y - mousePosStart.y;
215 if (w < 0) {
216 x += w;
217 w = -w;
218 }
219 if (h < 0) {
220 y += h;
221 h = -h;
222 }
223
224 if (aspectRatio) {
225 // keep the aspect ration by shrinking the rectangle
226 double aspectRatio = (double)mv.getWidth()/mv.getHeight();
227 if ((double)w/h > aspectRatio) {
228 int neww = (int)(h*aspectRatio);
229 if (mousePos.x < mousePosStart.x)
230 x += w-neww;
231 w = neww;
232 } else {
233 int newh = (int)(w/aspectRatio);
234 if (mousePos.y < mousePosStart.y)
235 y += h-newh;
236 h = newh;
237 }
238 }
239
240 return new Rectangle(x,y,w,h);
241 }
242
243 /**
244 * If the action goes inactive, remove the selection rectangle from screen
245 */
246 public void propertyChange(PropertyChangeEvent evt) {
247 if (evt.getPropertyName().equals("active") && !(Boolean)evt.getNewValue() && mousePosStart != null) {
248 paintRect();
249 mousePosStart = null;
250 mousePos = null;
251 }
252 }
253
254 /**
255 * Return a list of all objects in the rectangle, respecting the different
256 * modifier.
257 * @param alt Whether the alt key was pressed, which means select all objects
258 * that are touched, instead those which are completly covered. Also
259 * select whole tracks instead of line segments.
260 */
261 public Collection<OsmPrimitive> getObjectsInRectangle(Rectangle r, boolean alt) {
262 Collection<OsmPrimitive> selection = new LinkedList<OsmPrimitive>();
263
264 // whether user only clicked, not dragged.
265 boolean clicked = r.width <= 2 && r.height <= 2;
266 Point center = new Point(r.x+r.width/2, r.y+r.height/2);
267
268 if (clicked) {
269 OsmPrimitive osm = mv.getNearest(center, alt);
270 if (osm != null)
271 selection.add(osm);
272 } else {
273 // nodes
274 for (Node n : Main.main.ds.nodes) {
275 if (r.contains(mv.getScreenPoint(n.coor)))
276 selection.add(n);
277 }
278
279 // pending line segments
280 for (LineSegment ls : Main.main.ds.pendingLineSegments())
281 if (rectangleContainLineSegment(r, alt, ls))
282 selection.add(ls);
283
284 // tracks
285 for (Track t : Main.main.ds.tracks()) {
286 boolean wholeTrackSelected = !t.segments().isEmpty();
287 for (LineSegment ls : t.segments())
288 if (rectangleContainLineSegment(r, alt, ls))
289 selection.add(ls);
290 else
291 wholeTrackSelected = false;
292 if (wholeTrackSelected)
293 selection.add(t);
294 }
295
296 // TODO areas
297 }
298 return selection;
299 }
300
301 /**
302 * Decide whether the line segment is in the rectangle Return
303 * <code>true</code>, if it is in or false if not.
304 *
305 * @param r The rectangle, in which the line segment has to be.
306 * @param alt Whether user pressed the Alt key
307 * @param ls The line segment.
308 * @return <code>true</code>, if the LineSegment was added to the selection.
309 */
310 private boolean rectangleContainLineSegment(Rectangle r, boolean alt, LineSegment ls) {
311 if (alt) {
312 Point p1 = mv.getScreenPoint(ls.getStart().coor);
313 Point p2 = mv.getScreenPoint(ls.getEnd().coor);
314 if (r.intersectsLine(p1.x, p1.y, p2.x, p2.y))
315 return true;
316 } else {
317 if (r.contains(mv.getScreenPoint(ls.getStart().coor))
318 && r.contains(mv.getScreenPoint(ls.getEnd().coor)))
319 return true;
320 }
321 return false;
322 }
323
324
325 /**
326 * Does nothing. Only to satisfy MouseListener
327 */
328 public void mouseClicked(MouseEvent e) {}
329 /**
330 * Does nothing. Only to satisfy MouseListener
331 */
332 public void mouseEntered(MouseEvent e) {}
333 /**
334 * Does nothing. Only to satisfy MouseListener
335 */
336 public void mouseExited(MouseEvent e) {}
337 /**
338 * Does nothing. Only to satisfy MouseMotionListener
339 */
340 public void mouseMoved(MouseEvent e) {}
341
342}
Note: See TracBrowser for help on using the repository browser.