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

Last change on this file since 15 was 15, checked in by imi, 20 years ago

renamed alot (Layer instead of MapView) and removed feature of having
projections on every Layer.

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 Layer, the selection rectangle is drawn onto.
91 */
92 private final Layer 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 layer The view, the rectangle is drawn onto.
107 */
108 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, Layer layer) {
109 this.selectionEndedListener = selectionEndedListener;
110 this.aspectRatio = aspectRatio;
111 this.mv = layer;
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.