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

Last change on this file since 343 was 343, checked in by gebner, 17 years ago

Merge 0.5.

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