source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java@ 1421

Last change on this file since 1421 was 1421, checked in by stoecker, 15 years ago

close #2196 again. Patch by xeen

  • Property svn:eol-style set to native
File size: 17.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Cursor;
7import java.awt.Point;
8import java.awt.Rectangle;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseEvent;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.LinkedList;
16import java.util.List;
17
18import javax.swing.JOptionPane;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.actions.MergeNodesAction;
22import org.openstreetmap.josm.command.AddCommand;
23import org.openstreetmap.josm.command.ChangeCommand;
24import org.openstreetmap.josm.command.Command;
25import org.openstreetmap.josm.command.MoveCommand;
26import org.openstreetmap.josm.command.RotateCommand;
27import org.openstreetmap.josm.command.SequenceCommand;
28import org.openstreetmap.josm.data.coor.EastNorth;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.data.osm.Node;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.data.osm.WaySegment;
34import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
35import org.openstreetmap.josm.data.osm.visitor.SimplePaintVisitor;
36import org.openstreetmap.josm.gui.MapFrame;
37import org.openstreetmap.josm.gui.MapView;
38import org.openstreetmap.josm.gui.SelectionManager;
39import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
40import org.openstreetmap.josm.gui.layer.Layer;
41import org.openstreetmap.josm.gui.layer.OsmDataLayer;
42import org.openstreetmap.josm.tools.ImageProvider;
43import org.openstreetmap.josm.tools.Shortcut;
44
45/**
46 * Move is an action that can move all kind of OsmPrimitives (except keys for now).
47 *
48 * If an selected object is under the mouse when dragging, move all selected objects.
49 * If an unselected object is under the mouse when dragging, it becomes selected
50 * and will be moved.
51 * If no object is under the mouse, move all selected objects (if any)
52 *
53 * @author imi
54 */
55public class SelectAction extends MapMode implements SelectionEnded {
56
57 enum Mode { move, rotate, select }
58 private Mode mode = null;
59 private long mouseDownTime = 0;
60 private boolean didMove = false;
61 private boolean cancelDrawMode = false;
62 Node virtualNode = null;
63 WaySegment virtualWay = null;
64 SequenceCommand virtualCmds = null;
65
66 /**
67 * The old cursor before the user pressed the mouse button.
68 */
69 private Cursor oldCursor;
70 /**
71 * The position of the mouse before the user moves a node.
72 */
73 private Point mousePos;
74 private SelectionManager selectionManager;
75
76 /**
77 * The time which needs to pass between click and release before something
78 * counts as a move, in milliseconds
79 */
80 private int initialMoveDelay;
81
82 /**
83 * The screen distance which needs to be travelled before something
84 * counts as a move, in pixels
85 */
86 private int initialMoveThreshold;
87 private boolean initialMoveThresholdExceeded = false;
88 /**
89 * Create a new SelectAction
90 * @param mapFrame The MapFrame this action belongs to.
91 */
92 public SelectAction(MapFrame mapFrame) {
93 super(tr("Select"), "move/move", tr("Select, move and rotate objects"),
94 Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select")), KeyEvent.VK_S, Shortcut.GROUP_EDIT),
95 mapFrame,
96 getCursor("normal", "selection", Cursor.DEFAULT_CURSOR));
97 putValue("help", "Action/Move/Move");
98 selectionManager = new SelectionManager(this, false, mapFrame.mapView);
99 initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
100 initialMoveThreshold = Main.pref.getInteger("edit.initial-move-threshold",5);
101 }
102
103 private static Cursor getCursor(String name, String mod, int def) {
104 try {
105 return ImageProvider.getCursor(name, mod);
106 } catch (Exception e) {
107 }
108 return Cursor.getPredefinedCursor(def);
109 }
110
111 private void setCursor(Cursor c) {
112 if (oldCursor == null) {
113 oldCursor = Main.map.mapView.getCursor();
114 Main.map.mapView.setCursor(c);
115 }
116 }
117
118 private void restoreCursor() {
119 if (oldCursor != null) {
120 Main.map.mapView.setCursor(oldCursor);
121 oldCursor = null;
122 }
123 }
124
125 @Override public void enterMode() {
126 super.enterMode();
127 Main.map.mapView.addMouseListener(this);
128 Main.map.mapView.addMouseMotionListener(this);
129 Main.map.mapView.enableVirtualNodes(
130 Main.pref.getInteger("mappaint.node.virtual-size", 8) != 0);
131 }
132
133 @Override public void exitMode() {
134 super.exitMode();
135 selectionManager.unregister(Main.map.mapView);
136 Main.map.mapView.removeMouseListener(this);
137 Main.map.mapView.removeMouseMotionListener(this);
138 Main.map.mapView.enableVirtualNodes(false);
139 }
140
141 /**
142 * If the left mouse button is pressed, move all currently selected
143 * objects (if one of them is under the mouse) or the current one under the
144 * mouse (which will become selected).
145 */
146 @Override public void mouseDragged(MouseEvent e) {
147 if(!Main.map.mapView.isVisibleDrawableLayer())
148 return;
149
150 cancelDrawMode = true;
151 if (mode == Mode.select) return;
152
153 // do not count anything as a move if it lasts less than 100 milliseconds.
154 if ((mode == Mode.move) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
155
156 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
157 return;
158
159 if (mode == Mode.move) {
160 setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
161 }
162
163 if (mousePos == null) {
164 mousePos = e.getPoint();
165 return;
166 }
167
168 if (!initialMoveThresholdExceeded) {
169 int dxp = mousePos.x - e.getX();
170 int dyp = mousePos.y - e.getY();
171 int dp = (int) Math.sqrt(dxp*dxp+dyp*dyp);
172 if (dp < initialMoveThreshold) return;
173 initialMoveThresholdExceeded = true;
174 }
175
176 EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
177 EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
178 double dx = mouseEN.east() - mouseStartEN.east();
179 double dy = mouseEN.north() - mouseStartEN.north();
180 if (dx == 0 && dy == 0)
181 return;
182
183 if (virtualWay != null) {
184 Collection<Command> virtualCmds = new LinkedList<Command>();
185 virtualCmds.add(new AddCommand(virtualNode));
186 Way w = virtualWay.way;
187 Way wnew = new Way(w);
188 wnew.addNode(virtualWay.lowerIndex+1, virtualNode);
189 virtualCmds.add(new ChangeCommand(w, wnew));
190 virtualCmds.add(new MoveCommand(virtualNode, dx, dy));
191 Main.main.undoRedo.add(new SequenceCommand(tr("Add and move a virtual new node to way"), virtualCmds));
192 selectPrims(Collections.singleton((OsmPrimitive)virtualNode), false, false);
193 virtualWay = null;
194 virtualNode = null;
195 } else {
196 // Currently we support moving and rotating, which do not affect relations.
197 // So don't add them in the first place to make handling easier
198 Collection<OsmPrimitive> selection = Main.ds.getSelectedPhysical();
199 Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
200
201 // when rotating, having only one node makes no sense - quit silently
202 if (mode == Mode.rotate && affectedNodes.size() < 2)
203 return;
204
205 Command c = !Main.main.undoRedo.commands.isEmpty()
206 ? Main.main.undoRedo.commands.getLast() : null;
207 if (c instanceof SequenceCommand)
208 c = ((SequenceCommand)c).getLastCommand();
209
210 if (mode == Mode.move) {
211 if (c instanceof MoveCommand && affectedNodes.equals(((MoveCommand)c).objects))
212 ((MoveCommand)c).moveAgain(dx,dy);
213 else
214 Main.main.undoRedo.add(
215 c = new MoveCommand(selection, dx, dy));
216
217 for (Node n : affectedNodes) {
218 if (n.coor.isOutSideWorld()) {
219 // Revert move
220 ((MoveCommand) c).moveAgain(-dx, -dy);
221
222 JOptionPane.showMessageDialog(Main.parent,
223 tr("Cannot move objects outside of the world."));
224 return;
225 }
226 }
227 } else if (mode == Mode.rotate) {
228 if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).objects))
229 ((RotateCommand)c).rotateAgain(mouseStartEN, mouseEN);
230 else
231 Main.main.undoRedo.add(new RotateCommand(selection, mouseStartEN, mouseEN));
232 }
233 }
234
235 Main.map.mapView.repaint();
236 mousePos = e.getPoint();
237
238 didMove = true;
239 }
240
241 private Collection<OsmPrimitive> getNearestCollectionVirtual(Point p) {
242 MapView c = Main.map.mapView;
243 int snapDistance = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8);
244 snapDistance *= snapDistance;
245 OsmPrimitive osm = c.getNearestNode(p);
246 virtualWay = null;
247 virtualNode = null;
248
249 if (osm == null)
250 {
251 WaySegment nearestWaySeg = c.getNearestWaySegment(p);
252 if (nearestWaySeg != null)
253 {
254 osm = nearestWaySeg.way;
255 if(Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0)
256 {
257 Way w = (Way)osm;
258 Point p1 = c.getPoint(w.nodes.get(nearestWaySeg.lowerIndex).eastNorth);
259 Point p2 = c.getPoint(w.nodes.get(nearestWaySeg.lowerIndex+1).eastNorth);
260 if(SimplePaintVisitor.isLargeSegment(p1, p2, Main.pref.getInteger("mappaint.node.virtual-space", 70)))
261 {
262 Point pc = new Point((p1.x+p2.x)/2, (p1.y+p2.y)/2);
263 if (p.distanceSq(pc) < snapDistance)
264 {
265 virtualWay = nearestWaySeg;
266 virtualNode = new Node(Main.map.mapView.getLatLon(pc.x, pc.y));
267 osm = w;
268 }
269 }
270 }
271 }
272 }
273 if (osm == null)
274 return Collections.emptySet();
275 return Collections.singleton(osm);
276 }
277
278 /**
279 * Look, whether any object is selected. If not, select the nearest node.
280 * If there are no nodes in the dataset, do nothing.
281 *
282 * If the user did not press the left mouse button, do nothing.
283 *
284 * Also remember the starting position of the movement and change the mouse
285 * cursor to movement.
286 */
287 @Override public void mousePressed(MouseEvent e) {
288 if(!Main.map.mapView.isVisibleDrawableLayer())
289 return;
290
291 cancelDrawMode = false;
292 if (! (Boolean)this.getValue("active")) return;
293 if (e.getButton() != MouseEvent.BUTTON1)
294 return;
295 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
296 // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
297 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
298
299 // We don't want to change to draw tool if the user tries to (de)select
300 // stuff but accidentally clicks in an empty area when selection is empty
301 if(shift || ctrl)
302 cancelDrawMode = true;
303
304 mouseDownTime = System.currentTimeMillis();
305 didMove = false;
306 initialMoveThresholdExceeded = false;
307
308 Collection<OsmPrimitive> osmColl = getNearestCollectionVirtual(e.getPoint());
309
310 if (ctrl && shift) {
311 if (Main.ds.getSelected().isEmpty()) selectPrims(osmColl, true, false);
312 mode = Mode.rotate;
313 setCursor(ImageProvider.getCursor("rotate", null));
314 } else if (!osmColl.isEmpty()) {
315 // Don't replace the selection now if the user clicked on a
316 // selected object (this would break moving of selected groups).
317 // We'll do that later in mouseReleased if the user didn't try to
318 // move.
319 selectPrims(osmColl,
320 shift || Main.ds.getSelected().containsAll(osmColl),
321 ctrl);
322 mode = Mode.move;
323 } else {
324 mode = Mode.select;
325 oldCursor = Main.map.mapView.getCursor();
326 selectionManager.register(Main.map.mapView);
327 selectionManager.mousePressed(e);
328 }
329 if(mode != Mode.move || shift || ctrl)
330 {
331 virtualNode = null;
332 virtualWay = null;
333 }
334
335 updateStatusLine();
336 Main.map.mapView.repaint();
337
338 mousePos = e.getPoint();
339 }
340
341 /**
342 * Restore the old mouse cursor.
343 */
344 @Override public void mouseReleased(MouseEvent e) {
345 if(!Main.map.mapView.isVisibleDrawableLayer())
346 return;
347
348 if (mode == Mode.select) {
349 selectionManager.unregister(Main.map.mapView);
350
351 // Select Draw Tool if no selection has been made
352 if(Main.ds.getSelected().size() == 0 && !cancelDrawMode) {
353 Main.map.selectDrawTool(true);
354 return;
355 }
356 }
357 restoreCursor();
358
359 if (mode == Mode.move) {
360 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
361 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
362 if (!didMove) {
363 selectPrims(
364 Main.map.mapView.getNearestCollection(e.getPoint()),
365 shift, ctrl);
366
367 // If the user double-clicked a node, change to draw mode
368 List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelected());
369 if(e.getClickCount() >=2 && sel.size() == 1 && sel.get(0) instanceof Node) {
370 // We need to do it like this as otherwise drawAction will see a double
371 // click and switch back to SelectMode
372 Main.worker.execute(new Runnable(){
373 public void run() {
374 Main.map.selectDrawTool(true);
375 }
376 });
377 return;
378 }
379 } else {
380 Collection<OsmPrimitive> selection = Main.ds.getSelected();
381 if (ctrl) {
382 Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
383 Collection<Node> nn = Main.map.mapView.getNearestNodes(e.getPoint(), affectedNodes);
384 if (nn != null) {
385 Node n = nn.iterator().next();
386 LinkedList<Node> selNodes = new LinkedList<Node>();
387 for (OsmPrimitive osm : selection)
388 if (osm instanceof Node)
389 selNodes.add((Node)osm);
390 if (selNodes.size() > 0) {
391 selNodes.add(n);
392 MergeNodesAction.mergeNodes(selNodes, n);
393 }
394 }
395 }
396 DataSet.fireSelectionChanged(selection);
397 }
398 }
399
400 updateStatusLine();
401 mode = null;
402 updateStatusLine();
403 }
404
405 public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl) {
406 selectPrims(selectionManager.getObjectsInRectangle(r, alt), shift, ctrl);
407 }
408
409 public void selectPrims(Collection<OsmPrimitive> selectionList, boolean shift, boolean ctrl) {
410 if (shift && ctrl)
411 return; // not allowed together
412
413 Collection<OsmPrimitive> curSel;
414 if (!ctrl && !shift)
415 curSel = new LinkedList<OsmPrimitive>(); // new selection will replace the old.
416 else
417 curSel = Main.ds.getSelected();
418
419 for (OsmPrimitive osm : selectionList)
420 if (ctrl)
421 curSel.remove(osm);
422 else
423 curSel.add(osm);
424 Main.ds.setSelected(curSel);
425 Main.map.mapView.repaint();
426 }
427
428 @Override public String getModeHelpText() {
429 if (mode == Mode.select) {
430 return tr("Release the mouse button to select the objects in the rectangle.");
431 } else if (mode == Mode.move) {
432 return tr("Release the mouse button to stop moving. Ctrl to merge with nearest node.");
433 } else if (mode == Mode.rotate) {
434 return tr("Release the mouse button to stop rotating.");
435 } else {
436 return tr("Move objects by dragging; Shift to add to selection (Ctrl to remove); Shift-Ctrl to rotate selected; or change selection");
437 }
438 }
439
440 @Override public boolean layerIsSupported(Layer l) {
441 return l instanceof OsmDataLayer;
442 }
443}
Note: See TracBrowser for help on using the repository browser.