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

Last change on this file since 319 was 298, checked in by imi, 18 years ago
  • added license description to head of each source file
File size: 11.4 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.ActionEvent;
10import java.awt.event.InputEvent;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseEvent;
13import java.awt.event.MouseListener;
14import java.awt.event.MouseMotionListener;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.util.Collection;
18import java.util.LinkedList;
19
20import javax.swing.AbstractAction;
21import javax.swing.JComponent;
22import javax.swing.KeyStroke;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Segment;
28import org.openstreetmap.josm.data.osm.Way;
29
30/**
31 * Manages the selection of a rectangle. Listening to left and right mouse button
32 * presses and to mouse motions and draw the rectangle accordingly.
33 *
34 * Left mouse button selects a rectangle from the press until release. Pressing
35 * right mouse button while left is still pressed enable the rectangle to move
36 * around. Releasing the left button fires an action event to the listener given
37 * at constructor, except if the right is still pressed, which just remove the
38 * selection rectangle and does nothing.
39 *
40 * The point where the left mouse button was pressed and the current mouse
41 * position are two opposite corners of the selection rectangle.
42 *
43 * It is possible to specify an aspect ratio (width per height) which the
44 * selection rectangle always must have. In this case, the selection rectangle
45 * will be the largest window with this aspect ratio, where the position the left
46 * mouse button was pressed and the corner of the current mouse position are at
47 * opposite sites (the mouse position corner is the corner nearest to the mouse
48 * cursor).
49 *
50 * When the left mouse button was released, an ActionEvent is send to the
51 * ActionListener given at constructor. The source of this event is this manager.
52 *
53 * @author imi
54 */
55public class SelectionManager implements MouseListener, MouseMotionListener, PropertyChangeListener {
56
57 /**
58 * This is the interface that an user of SelectionManager has to implement
59 * to get informed when a selection closes.
60 * @author imi
61 */
62 public interface SelectionEnded {
63 /**
64 * Called, when the left mouse button was released.
65 * @param r The rectangle, that is currently the selection.
66 * @param alt Whether the alt key was pressed
67 * @param shift Whether the shift key was pressed
68 * @param ctrl Whether the ctrl key was pressed
69 * @see InputEvent#getModifiersEx()
70 */
71 public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl);
72 /**
73 * Called to register the selection manager for "active" property.
74 * @param listener The listener to register
75 */
76 public void addPropertyChangeListener(PropertyChangeListener listener);
77 /**
78 * Called to remove the selection manager from the listener list
79 * for "active" property.
80 * @param listener The listener to register
81 */
82 public void removePropertyChangeListener(PropertyChangeListener listener);
83 }
84 /**
85 * The listener that receives the events after left mouse button is released.
86 */
87 private final SelectionEnded selectionEndedListener;
88 /**
89 * Position of the map when the mouse button was pressed.
90 * If this is not <code>null</code>, a rectangle is drawn on screen.
91 */
92 private Point mousePosStart;
93 /**
94 * Position of the map when the selection rectangle was last drawn.
95 */
96 private Point mousePos;
97 /**
98 * The Component, the selection rectangle is drawn onto.
99 */
100 private final NavigatableComponent nc;
101 /**
102 * Whether the selection rectangle must obtain the aspect ratio of the
103 * drawComponent.
104 */
105 private boolean aspectRatio;
106
107 /**
108 * Create a new SelectionManager.
109 *
110 * @param actionListener The action listener that receives the event when
111 * the left button is released.
112 * @param aspectRatio If true, the selection window must obtain the aspect
113 * ratio of the drawComponent.
114 * @param navComp The component, the rectangle is drawn onto.
115 */
116 public SelectionManager(SelectionEnded selectionEndedListener, boolean aspectRatio, NavigatableComponent navComp) {
117 this.selectionEndedListener = selectionEndedListener;
118 this.aspectRatio = aspectRatio;
119 this.nc = navComp;
120 }
121
122 /**
123 * Register itself at the given event source.
124 * @param eventSource The emitter of the mouse events.
125 */
126 public void register(NavigatableComponent eventSource) {
127 eventSource.addMouseListener(this);
128 eventSource.addMouseMotionListener(this);
129 selectionEndedListener.addPropertyChangeListener(this);
130 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "SelectionManager");
131 Main.contentPane.getActionMap().put("SelectionManager", new AbstractAction(){
132 public void actionPerformed(ActionEvent e) {
133 if (mousePos != null && mousePosStart != null)
134 paintRect();
135 mousePosStart = null;
136 mousePos = null;
137 }
138 });
139 eventSource.addPropertyChangeListener("scale", new PropertyChangeListener(){
140 public void propertyChange(PropertyChangeEvent evt) {
141 if (mousePosStart != null) {
142 paintRect();
143 mousePos = mousePosStart = null;
144 }
145 }
146 });
147 }
148 /**
149 * Unregister itself from the given event source. If a selection rectangle is
150 * shown, hide it first.
151 *
152 * @param eventSource The emitter of the mouse events.
153 */
154 public void unregister(Component eventSource) {
155 eventSource.removeMouseListener(this);
156 eventSource.removeMouseMotionListener(this);
157 selectionEndedListener.removePropertyChangeListener(this);
158 Main.contentPane.getInputMap().remove(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
159 Main.contentPane.getActionMap().remove("SelectionManager");
160 }
161
162 /**
163 * If the correct button, from the "drawing rectangle" mode
164 */
165 public void mousePressed(MouseEvent e) {
166 if (e.getButton() == MouseEvent.BUTTON1)
167 mousePosStart = mousePos = e.getPoint();
168 }
169
170 /**
171 * If the correct button is hold, draw the rectangle.
172 */
173 public void mouseDragged(MouseEvent e) {
174 int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK);
175
176
177 if (buttonPressed != 0) {
178 if (mousePosStart == null)
179 mousePosStart = mousePos = e.getPoint();
180 paintRect();
181 }
182
183 if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) {
184 mousePos = e.getPoint();
185 paintRect();
186 } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) {
187 mousePosStart.x += e.getX()-mousePos.x;
188 mousePosStart.y += e.getY()-mousePos.y;
189 mousePos = e.getPoint();
190 paintRect();
191 }
192 }
193
194 /**
195 * Check the state of the keys and buttons and set the selection accordingly.
196 */
197 public void mouseReleased(MouseEvent e) {
198 if (e.getButton() != MouseEvent.BUTTON1)
199 return;
200 if (mousePos == null || mousePosStart == null)
201 return; // injected release from outside
202
203 // disable the selection rect
204 paintRect();
205 Rectangle r = getSelectionRectangle();
206 mousePosStart = null;
207 mousePos = null;
208
209 boolean shift = (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0;
210 boolean alt = (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0;
211 boolean ctrl = (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0;
212 if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0)
213 selectionEndedListener.selectionEnded(r, alt, shift, ctrl);
214 }
215
216
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);
227
228 Rectangle r = getSelectionRectangle();
229 g.drawRect(r.x,r.y,r.width,r.height);
230 }
231
232 /**
233 * Calculate and return the current selection rectangle
234 * @return A rectangle that spans from mousePos to mouseStartPos
235 */
236 private Rectangle getSelectionRectangle() {
237 int x = mousePosStart.x;
238 int y = mousePosStart.y;
239 int w = mousePos.x - mousePosStart.x;
240 int h = mousePos.y - mousePosStart.y;
241 if (w < 0) {
242 x += w;
243 w = -w;
244 }
245 if (h < 0) {
246 y += h;
247 h = -h;
248 }
249
250 if (aspectRatio) {
251 // keep the aspect ration by shrinking the rectangle
252 double aspectRatio = (double)nc.getWidth()/nc.getHeight();
253 if ((double)w/h > aspectRatio) {
254 int neww = (int)(h*aspectRatio);
255 if (mousePos.x < mousePosStart.x)
256 x += w-neww;
257 w = neww;
258 } else {
259 int newh = (int)(w/aspectRatio);
260 if (mousePos.y < mousePosStart.y)
261 y += h-newh;
262 h = newh;
263 }
264 }
265
266 return new Rectangle(x,y,w,h);
267 }
268
269 /**
270 * If the action goes inactive, remove the selection rectangle from screen
271 */
272 public void propertyChange(PropertyChangeEvent evt) {
273 if (evt.getPropertyName().equals("active") && !(Boolean)evt.getNewValue() && mousePosStart != null) {
274 paintRect();
275 mousePosStart = null;
276 mousePos = null;
277 }
278 }
279
280 /**
281 * Return a list of all objects in the rectangle, respecting the different
282 * modifier.
283 * @param alt Whether the alt key was pressed, which means select all objects
284 * that are touched, instead those which are completly covered. Also
285 * select whole ways instead of segments.
286 */
287 public Collection<OsmPrimitive> getObjectsInRectangle(Rectangle r, boolean alt) {
288 Collection<OsmPrimitive> selection = new LinkedList<OsmPrimitive>();
289
290 // whether user only clicked, not dragged.
291 boolean clicked = r.width <= 2 && r.height <= 2;
292 Point center = new Point(r.x+r.width/2, r.y+r.height/2);
293
294 if (clicked) {
295 OsmPrimitive osm = nc.getNearest(center, alt);
296 if (osm != null)
297 selection.add(osm);
298 } else {
299 // nodes
300 for (Node n : Main.ds.nodes) {
301 if (!n.deleted && r.contains(nc.getPoint(n.eastNorth)))
302 selection.add(n);
303 }
304
305 // pending segments
306 for (Segment s : Main.ds.segments)
307 if (!s.deleted && rectangleContainSegment(r, alt, s))
308 selection.add(s);
309
310 // ways
311 for (Way w : Main.ds.ways) {
312 if (w.deleted)
313 continue;
314 boolean someSelectableSegment = false;
315 boolean wholeWaySelected = true;
316 for (Segment s : w.segments) {
317 if (s.incomplete)
318 continue;
319 someSelectableSegment = true;
320 if (!rectangleContainSegment(r, alt, s)) {
321 wholeWaySelected = false;
322 break;
323 }
324 }
325 if (someSelectableSegment && wholeWaySelected)
326 selection.add(w);
327 }
328 }
329 return selection;
330 }
331
332 /**
333 * Decide whether the segment is in the rectangle Return
334 * <code>true</code>, if it is in or false if not.
335 *
336 * @param r The rectangle, in which the segment has to be.
337 * @param alt Whether user pressed the Alt key
338 * @param ls The segment.
339 * @return <code>true</code>, if the Segment was added to the selection.
340 */
341 private boolean rectangleContainSegment(Rectangle r, boolean alt, Segment ls) {
342 if (ls.incomplete)
343 return false;
344 if (alt) {
345 Point p1 = nc.getPoint(ls.from.eastNorth);
346 Point p2 = nc.getPoint(ls.to.eastNorth);
347 if (r.intersectsLine(p1.x, p1.y, p2.x, p2.y))
348 return true;
349 } else {
350 if (r.contains(nc.getPoint(ls.from.eastNorth))
351 && r.contains(nc.getPoint(ls.to.eastNorth)))
352 return true;
353 }
354 return false;
355 }
356
357 public void mouseClicked(MouseEvent e) {}
358 public void mouseEntered(MouseEvent e) {}
359 public void mouseExited(MouseEvent e) {}
360 public void mouseMoved(MouseEvent e) {}
361}
Note: See TracBrowser for help on using the repository browser.