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

Last change on this file since 3965 was 3919, checked in by stoecker, 13 years ago

unify cursor handling, hopefully fix #5381

  • Property svn:eol-style set to native
File size: 28.2 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Cursor;
9import java.awt.Point;
10import java.awt.Rectangle;
11import java.awt.event.ActionEvent;
12import java.awt.event.InputEvent;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.awt.geom.Point2D;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.Iterator;
19import java.util.LinkedList;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.MergeNodesAction;
25import org.openstreetmap.josm.command.AddCommand;
26import org.openstreetmap.josm.command.ChangeCommand;
27import org.openstreetmap.josm.command.Command;
28import org.openstreetmap.josm.command.MoveCommand;
29import org.openstreetmap.josm.command.RotateCommand;
30import org.openstreetmap.josm.command.ScaleCommand;
31import org.openstreetmap.josm.command.SequenceCommand;
32import org.openstreetmap.josm.data.coor.EastNorth;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.osm.Node;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.data.osm.Way;
37import org.openstreetmap.josm.data.osm.WaySegment;
38import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
39import org.openstreetmap.josm.data.osm.visitor.paint.SimplePaintVisitor;
40import org.openstreetmap.josm.gui.ExtendedDialog;
41import org.openstreetmap.josm.gui.MapFrame;
42import org.openstreetmap.josm.gui.MapView;
43import org.openstreetmap.josm.gui.SelectionManager;
44import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
45import org.openstreetmap.josm.gui.layer.Layer;
46import org.openstreetmap.josm.gui.layer.OsmDataLayer;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.openstreetmap.josm.tools.Pair;
49import org.openstreetmap.josm.tools.PlatformHookOsx;
50import org.openstreetmap.josm.tools.Shortcut;
51
52/**
53 * Move is an action that can move all kind of OsmPrimitives (except keys for now).
54 *
55 * If an selected object is under the mouse when dragging, move all selected objects.
56 * If an unselected object is under the mouse when dragging, it becomes selected
57 * and will be moved.
58 * If no object is under the mouse, move all selected objects (if any)
59 *
60 * @author imi
61 */
62public class SelectAction extends MapMode implements SelectionEnded {
63 //static private final Logger logger = Logger.getLogger(SelectAction.class.getName());
64
65 enum Mode { move, rotate, scale, select }
66
67 private Mode mode = null;
68 private SelectionManager selectionManager;
69 private boolean cancelDrawMode = false;
70 private boolean didMouseDrag = false;
71 /**
72 * The component this SelectAction is associated with.
73 */
74 private final MapView mv;
75 /**
76 * The old cursor before the user pressed the mouse button.
77 */
78 private Point startingDraggingPos;
79 /**
80 * The last known position of the mouse.
81 */
82 private Point lastMousePos;
83 /**
84 * The time of the user mouse down event.
85 */
86 private long mouseDownTime = 0;
87 /**
88 * The time which needs to pass between click and release before something
89 * counts as a move, in milliseconds
90 */
91 private int initialMoveDelay;
92 /**
93 * The screen distance which needs to be travelled before something
94 * counts as a move, in pixels
95 */
96 private int initialMoveThreshold;
97 private boolean initialMoveThresholdExceeded = false;
98
99 /**
100 * Create a new SelectAction
101 * @param mapFrame The MapFrame this action belongs to.
102 */
103 public SelectAction(MapFrame mapFrame) {
104 super(tr("Select"), "move/move", tr("Select, move, scale and rotate objects"),
105 Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select")), KeyEvent.VK_S, Shortcut.GROUP_EDIT),
106 mapFrame,
107 ImageProvider.getCursor("normal", "selection"));
108 mv = mapFrame.mapView;
109 putValue("help", ht("/Action/Move/Move"));
110 selectionManager = new SelectionManager(this, false, mv);
111 initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay", 200);
112 initialMoveThreshold = Main.pref.getInteger("edit.initial-move-threshold", 5);
113 }
114
115 @Override
116 public void enterMode() {
117 super.enterMode();
118 mv.addMouseListener(this);
119 mv.addMouseMotionListener(this);
120 mv.setVirtualNodesEnabled(
121 Main.pref.getInteger("mappaint.node.virtual-size", 8) != 0);
122 }
123
124 @Override
125 public void exitMode() {
126 super.exitMode();
127 selectionManager.unregister(mv);
128 mv.removeMouseListener(this);
129 mv.removeMouseMotionListener(this);
130 mv.setVirtualNodesEnabled(false);
131 }
132
133 /**
134 * If the left mouse button is pressed, move all currently selected
135 * objects (if one of them is under the mouse) or the current one under the
136 * mouse (which will become selected).
137 */
138 @Override
139 public void mouseDragged(MouseEvent e) {
140 if (!mv.isActiveLayerVisible())
141 return;
142
143 cancelDrawMode = true;
144 if (mode == Mode.select)
145 return;
146
147 // do not count anything as a move if it lasts less than 100 milliseconds.
148 if ((mode == Mode.move) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay))
149 return;
150
151 if (mode != Mode.rotate && mode != Mode.scale) // button is pressed in rotate mode
152 {
153 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
154 return;
155 }
156
157 if (mode == Mode.move) {
158 mv.setNewCursor(Cursor.MOVE_CURSOR, this);
159 }
160
161 if (startingDraggingPos == null) {
162 startingDraggingPos = new Point(e.getX(), e.getY());
163 }
164
165 if (!initialMoveThresholdExceeded) {
166 int dxp = lastMousePos.x - e.getX();
167 int dyp = lastMousePos.y - e.getY();
168 int dp = (int) Math.sqrt(dxp * dxp + dyp * dyp);
169 if (dp < initialMoveThreshold)
170 return;
171 initialMoveThresholdExceeded = true;
172 }
173
174 EastNorth currentEN = mv.getEastNorth(e.getX(), e.getY());
175 EastNorth lastEN = mv.getEastNorth(lastMousePos.x, lastMousePos.y);
176 //EastNorth startEN = mv.getEastNorth(startingDraggingPos.x, startingDraggingPos.y);
177 double dx = currentEN.east() - lastEN.east();
178 double dy = currentEN.north() - lastEN.north();
179 if (dx == 0 && dy == 0)
180 return;
181
182 if (virtualWays.size() > 0) {
183 Collection<Command> virtualCmds = new LinkedList<Command>();
184 virtualCmds.add(new AddCommand(virtualNode));
185 for (WaySegment virtualWay : virtualWays) {
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 }
191 virtualCmds.add(new MoveCommand(virtualNode, dx, dy));
192 String text = trn("Add and move a virtual new node to way",
193 "Add and move a virtual new node to {0} ways", virtualWays.size(),
194 virtualWays.size());
195 Main.main.undoRedo.add(new SequenceCommand(text, virtualCmds));
196 getCurrentDataSet().setSelected(Collections.singleton((OsmPrimitive) virtualNode));
197 virtualWays.clear();
198 virtualNode = null;
199 } else {
200 // Currently we support only transformations which do not affect relations.
201 // So don't add them in the first place to make handling easier
202 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelectedNodesAndWays();
203 Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
204
205 // for these transformations, having only one node makes no sense - quit silently
206 if (affectedNodes.size() < 2 && (mode == Mode.rotate || mode == Mode.scale))
207 return;
208
209 Command c = !Main.main.undoRedo.commands.isEmpty()
210 ? Main.main.undoRedo.commands.getLast() : null;
211 if (c instanceof SequenceCommand) {
212 c = ((SequenceCommand) c).getLastCommand();
213 }
214
215 if (mode == Mode.move) {
216 if (c instanceof MoveCommand && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) {
217 ((MoveCommand) c).moveAgain(dx, dy);
218 } else {
219 Main.main.undoRedo.add(
220 c = new MoveCommand(selection, dx, dy));
221 }
222
223 for (Node n : affectedNodes) {
224 if (n.getCoor().isOutSideWorld()) {
225 // Revert move
226 ((MoveCommand) c).moveAgain(-dx, -dy);
227
228 JOptionPane.showMessageDialog(
229 Main.parent,
230 tr("Cannot move objects outside of the world."),
231 tr("Warning"),
232 JOptionPane.WARNING_MESSAGE);
233 mv.setNewCursor(cursor, this);
234 return;
235 }
236 }
237 } else if (mode == Mode.rotate) {
238 if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand) c).getTransformedNodes())) {
239 ((RotateCommand) c).handleEvent(currentEN);
240 } else {
241 Main.main.undoRedo.add(new RotateCommand(selection, currentEN));
242 }
243 } else if (mode == Mode.scale) {
244 if (c instanceof ScaleCommand && affectedNodes.equals(((ScaleCommand) c).getTransformedNodes())) {
245 ((ScaleCommand) c).handleEvent(currentEN);
246 } else {
247 Main.main.undoRedo.add(new ScaleCommand(selection, currentEN));
248 }
249 }
250 }
251
252 mv.repaint();
253 if (mode != Mode.scale) {
254 lastMousePos = e.getPoint();
255 }
256
257 didMouseDrag = true;
258 }
259
260 @Override
261 public void mouseMoved(MouseEvent e) {
262 // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired.
263 //
264 if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale)) {
265 mouseDragged(e);
266 }
267 }
268 private Node virtualNode = null;
269 private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>();
270
271 /**
272 * Calculate a virtual node if there is enough visual space to draw a crosshair
273 * node and the middle of a way segment is clicked. If the user drags the
274 * crosshair node, it will be added to all ways in <code>virtualWays</code>.
275 *
276 * @param e contains the point clicked
277 * @return whether <code>virtualNode</code> and <code>virtualWays</code> were setup.
278 */
279 private boolean setupVirtual(MouseEvent e) {
280 if (Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0) {
281 int virtualSnapDistSq = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8);
282 int virtualSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
283 virtualSnapDistSq *= virtualSnapDistSq;
284
285 Collection<WaySegment> selVirtualWays = new LinkedList<WaySegment>();
286 Pair<Node, Node> vnp = null, wnp = new Pair<Node, Node>(null, null);
287 Point p = e.getPoint();
288 Way w = null;
289
290 for (WaySegment ws : mv.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate)) {
291 w = ws.way;
292
293 Point2D p1 = mv.getPoint2D(wnp.a = w.getNode(ws.lowerIndex));
294 Point2D p2 = mv.getPoint2D(wnp.b = w.getNode(ws.lowerIndex + 1));
295 if (SimplePaintVisitor.isLargeSegment(p1, p2, virtualSpace)) {
296 Point2D pc = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
297 if (p.distanceSq(pc) < virtualSnapDistSq) {
298 // Check that only segments on top of each other get added to the
299 // virtual ways list. Otherwise ways that coincidentally have their
300 // virtual node at the same spot will be joined which is likely unwanted
301 Pair.sort(wnp);
302 if (vnp == null) {
303 vnp = new Pair<Node, Node>(wnp.a, wnp.b);
304 virtualNode = new Node(mv.getLatLon(pc.getX(), pc.getY()));
305 }
306 if (vnp.equals(wnp)) {
307 (w.isSelected() ? selVirtualWays : virtualWays).add(ws);
308 }
309 }
310 }
311 }
312
313 if (!selVirtualWays.isEmpty()) {
314 virtualWays = selVirtualWays;
315 }
316 }
317
318 return !virtualWays.isEmpty();
319 }
320 private Collection<OsmPrimitive> cycleList = Collections.emptyList();
321 private boolean cyclePrims = false;
322 private OsmPrimitive cycleStart = null;
323
324 /**
325 *
326 * @param osm nearest primitive found by simple method
327 * @param e
328 * @return
329 */
330 private Collection<OsmPrimitive> cycleSetup(Collection<OsmPrimitive> single, MouseEvent e) {
331 OsmPrimitive osm = null;
332
333 if (single != null && !single.isEmpty()) {
334 osm = single.iterator().next();
335
336 Point p = e.getPoint();
337 boolean waitForMouseUp = Main.pref.getBoolean("mappaint.select.waits-for-mouse-up", false);
338 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
339 boolean alt = ((e.getModifiers() & (ActionEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0 || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false));
340
341 if (!alt) {
342 cycleList = MapView.asColl(osm);
343
344 if (waitForMouseUp) {
345 // prefer a selected nearest node or way, if possible
346 osm = mv.getNearestNodeOrWay(p, OsmPrimitive.isSelectablePredicate, true);
347 }
348 } else {
349 if (osm instanceof Node) {
350 cycleList = new LinkedList<OsmPrimitive>(mv.getNearestNodes(p, OsmPrimitive.isSelectablePredicate));
351 } else if (osm instanceof Way) {
352 cycleList = new LinkedList<OsmPrimitive>(mv.getNearestWays(p, OsmPrimitive.isSelectablePredicate));
353 }
354
355 if (cycleList.size() > 1) {
356 cyclePrims = false;
357
358 OsmPrimitive old = osm;
359 for (OsmPrimitive o : cycleList) {
360 if (o.isSelected()) {
361 cyclePrims = true;
362 osm = o;
363 break;
364 }
365 }
366
367 // special case: for cycle groups of 2, we can toggle to the
368 // true nearest primitive on mousePressed right away
369 if (cycleList.size() == 2 && !waitForMouseUp) {
370 if (!(osm.equals(old) || osm.isNew() || ctrl)) {
371 cyclePrims = false;
372 osm = old;
373 } // else defer toggling to mouseRelease time in those cases:
374 /*
375 * osm == old -- the true nearest node is the selected one
376 * osm is a new node -- do not break unglue ways in ALT mode
377 * ctrl is pressed -- ctrl generally works on mouseReleased
378 */
379 }
380 }
381 }
382 }
383
384 return MapView.asColl(osm);
385 }
386
387 /**
388 * Look, whether any object is selected. If not, select the nearest node.
389 * If there are no nodes in the dataset, do nothing.
390 *
391 * If the user did not press the left mouse button, do nothing.
392 *
393 * Also remember the starting position of the movement and change the mouse
394 * cursor to movement.
395 */
396 @Override
397 public void mousePressed(MouseEvent e) {
398 debug("mousePressed: e.getPoint()=" + e.getPoint());
399
400 // return early
401 if (!mv.isActiveLayerVisible() || !(Boolean) this.getValue("active") || e.getButton() != MouseEvent.BUTTON1) {
402 return;
403 }
404
405 // request focus in order to enable the expected keyboard shortcuts
406 mv.requestFocus();
407
408 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
409 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
410 boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
411
412 // We don't want to change to draw tool if the user tries to (de)select
413 // stuff but accidentally clicks in an empty area when selection is empty
414 cancelDrawMode = (shift || ctrl);
415 didMouseDrag = false;
416 initialMoveThresholdExceeded = false;
417 mouseDownTime = System.currentTimeMillis();
418 lastMousePos = e.getPoint();
419
420 Collection<OsmPrimitive> c = MapView.asColl(
421 mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false));
422
423 if (shift && ctrl) {
424 mode = Mode.rotate;
425
426 if (getCurrentDataSet().getSelected().isEmpty()) {
427 getCurrentDataSet().setSelected(c);
428 }
429
430 // Mode.select redraws when selectPrims is called
431 // Mode.move redraws when mouseDragged is called
432 // Mode.rotate redraws here
433 mv.setNewCursor(ImageProvider.getCursor("rotate", null), this);
434 mv.repaint();
435 } else if (alt && ctrl) {
436 mode = Mode.scale;
437
438 if (getCurrentDataSet().getSelected().isEmpty()) {
439 getCurrentDataSet().setSelected(c);
440 }
441
442 // Mode.select redraws when selectPrims is called
443 // Mode.move redraws when mouseDragged is called
444 // Mode.scale redraws here
445 mv.setNewCursor(ImageProvider.getCursor("scale", null), this);
446 mv.repaint();
447 } else if (!c.isEmpty()) {
448 mode = Mode.move;
449
450 if (!cancelDrawMode && c.iterator().next() instanceof Way) {
451 setupVirtual(e);
452 }
453
454 selectPrims(cycleSetup(c, e), e, false, false);
455 } else {
456 mode = Mode.select;
457
458 selectionManager.register(mv);
459 selectionManager.mousePressed(e);
460 }
461
462 updateStatusLine();
463 }
464
465 @Override
466 public void mouseReleased(MouseEvent e) {
467 debug("mouseReleased: e.getPoint()=" + e.getPoint());
468
469 if (!mv.isActiveLayerVisible()) {
470 return;
471 }
472
473 startingDraggingPos = null;
474
475 mv.setNewCursor(cursor, this);
476 if (mode == Mode.select) {
477 selectionManager.unregister(mv);
478
479 // Select Draw Tool if no selection has been made
480 if (getCurrentDataSet().getSelected().size() == 0 && !cancelDrawMode) {
481 Main.map.selectDrawTool(true);
482 return;
483 }
484 }
485
486 if (mode == Mode.move) {
487 if (!didMouseDrag) {
488 // only built in move mode
489 virtualWays.clear();
490 virtualNode = null;
491
492 // do nothing if the click was to short to be recognized as a drag,
493 // but the release position is farther than 10px away from the press position
494 if (lastMousePos.distanceSq(e.getPoint()) < 100) {
495 selectPrims(cyclePrims(cycleList, e), e, true, false);
496
497 // If the user double-clicked a node, change to draw mode
498 Collection<OsmPrimitive> c = getCurrentDataSet().getSelected();
499 if (e.getClickCount() >= 2 && c.size() == 1 && c.iterator().next() instanceof Node) {
500 // We need to do it like this as otherwise drawAction will see a double
501 // click and switch back to SelectMode
502 Main.worker.execute(new Runnable() {
503
504 public void run() {
505 Main.map.selectDrawTool(true);
506 }
507 });
508 return;
509 }
510 }
511 } else {
512 int max = Main.pref.getInteger("warn.move.maxelements", 20), limit = max;
513 for (OsmPrimitive osm : getCurrentDataSet().getSelected()) {
514 if (osm instanceof Way) {
515 limit -= ((Way) osm).getNodes().size();
516 }
517 if ((limit -= 1) < 0) {
518 break;
519 }
520 }
521 if (limit < 0) {
522 ExtendedDialog ed = new ExtendedDialog(
523 Main.parent,
524 tr("Move elements"),
525 new String[]{tr("Move them"), tr("Undo move")});
526 ed.setButtonIcons(new String[]{"reorder.png", "cancel.png"});
527 ed.setContent(tr("You moved more than {0} elements. " + "Moving a large number of elements is often an error.\n" + "Really move them?", max));
528 ed.setCancelButton(2);
529 ed.toggleEnable("movedManyElements");
530 ed.showDialog();
531
532 if (ed.getValue() != 1) {
533 Main.main.undoRedo.undo();
534 }
535 } else {
536 mergePrims(getCurrentDataSet().getSelectedNodes(), e);
537 }
538 getCurrentDataSet().fireSelectionChanged();
539 }
540 }
541
542 // I don't see why we need this.
543 //updateStatusLine();
544 mode = null;
545 updateStatusLine();
546 }
547
548 public void selectionEnded(Rectangle r, MouseEvent e) {
549 boolean alt = (e.getModifiersEx() & (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK)) != 0;
550 selectPrims(selectionManager.getObjectsInRectangle(r, alt), e, true, true);
551 }
552
553 /**
554 * Modifies current selection state and returns the next element in a
555 * selection cycle given by <code>prims</code>.
556 * @param prims the primitives that form the selection cycle
557 * @param shift whether shift is pressed
558 * @param ctrl whether ctrl is pressed
559 * @return the next element of cycle list <code>prims</code>.
560 */
561 private Collection<OsmPrimitive> cyclePrims(Collection<OsmPrimitive> prims, MouseEvent e) {
562 OsmPrimitive nxt = null;
563
564 debug("cyclePrims(): entry.....");
565 for (OsmPrimitive osm : prims) {
566 debug("cyclePrims(): prims id=" + osm.getId());
567 }
568
569 if (prims.size() > 1) {
570 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
571 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
572
573 DataSet ds = getCurrentDataSet();
574 OsmPrimitive first = prims.iterator().next(), foundInDS = null;
575 nxt = first;
576
577 for (Iterator<OsmPrimitive> i = prims.iterator(); i.hasNext();) {
578 if (cyclePrims && shift) {
579 if (!(nxt = i.next()).isSelected()) {
580 debug("cyclePrims(): taking " + nxt.getId());
581 break; // take first primitive in prims list not in sel
582 }
583 } else {
584 if ((nxt = i.next()).isSelected()) {
585 foundInDS = nxt;
586 if (cyclePrims || ctrl) {
587 ds.clearSelection(foundInDS);
588 nxt = i.hasNext() ? i.next() : first;
589 }
590 debug("selectPrims(): taking " + nxt.getId());
591 break; // take next primitive in prims list
592 }
593 }
594 }
595
596 if (ctrl) {
597 // a member of prims was found in the current dataset selection
598 if (foundInDS != null) {
599 // mouse was moved to a different selection group w/ a previous sel
600 if (!prims.contains(cycleStart)) {
601 ds.clearSelection(prims);
602 cycleStart = foundInDS;
603 debug("selectPrims(): cycleStart set to foundInDS=" + cycleStart.getId());
604 } else if (cycleStart.equals(nxt)) {
605 // loop detected, insert deselect step
606 ds.addSelected(nxt);
607 debug("selectPrims(): cycleStart hit");
608 }
609 } else {
610 // setup for iterating a sel group again or a new, different one..
611 nxt = (prims.contains(cycleStart)) ? cycleStart : first;
612 cycleStart = nxt;
613 debug("selectPrims(): cycleStart set to nxt=" + cycleStart.getId());
614 }
615 } else {
616 cycleStart = null;
617 }
618
619 debug("cyclePrims(): truncated prims list to id=" + nxt.getId());
620 }
621
622 // pass on prims, if it had less than 2 elements
623 return (nxt != null) ? MapView.asColl(nxt) : prims;
624 }
625
626 private void mergePrims(Collection<Node> affectedNodes, MouseEvent e) {
627 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
628
629 if (ctrl && !affectedNodes.isEmpty()) {
630 Collection<Node> target = mv.getNearestNodes(e.getPoint(), affectedNodes, OsmPrimitive.isSelectablePredicate);
631 if (!target.isEmpty()) {
632 Collection<Node> nodesToMerge = new LinkedList<Node>(affectedNodes);
633 nodesToMerge.add(target.iterator().next());
634
635 Command cmd = MergeNodesAction.mergeNodes(Main.main.getEditLayer(), nodesToMerge, target.iterator().next());
636 if (cmd != null) {
637 Main.main.undoRedo.add(cmd);
638 }
639 }
640 }
641 }
642
643 private void selectPrims(Collection<OsmPrimitive> prims, MouseEvent e, boolean released, boolean area) {
644 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
645 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
646 DataSet ds = getCurrentDataSet();
647
648 // not allowed together: do not change dataset selection, return early
649 if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty())) {
650 return;
651 }
652
653 if (!released) {
654 // Don't replace the selection if the user clicked on a
655 // selected object (it breaks moving of selected groups).
656 // Do it later, on mouse release.
657 shift |= getCurrentDataSet().getSelected().containsAll(prims);
658 }
659
660 if (ctrl) {
661 // Ctrl on an item toggles its selection status,
662 // but Ctrl on an *area* just clears those items
663 // out of the selection.
664 if (area) {
665 ds.clearSelection(prims);
666 } else {
667 ds.toggleSelected(prims);
668 }
669 } else if (shift) {
670 // add prims to an existing selection
671 ds.addSelected(prims);
672 } else {
673 // clear selection, then select the prims clicked
674 ds.setSelected(prims);
675 }
676 }
677
678 @Override
679 public String getModeHelpText() {
680 if (mode == Mode.select) {
681 return tr("Release the mouse button to select the objects in the rectangle.");
682 } else if (mode == Mode.move) {
683 return tr("Release the mouse button to stop moving. Ctrl to merge with nearest node.");
684 } else if (mode == Mode.rotate) {
685 return tr("Release the mouse button to stop rotating.");
686 } else if (mode == Mode.scale) {
687 return tr("Release the mouse button to stop scaling.");
688 } else {
689 return tr("Move objects by dragging; Shift to add to selection (Ctrl to toggle); Shift-Ctrl to rotate selected; Alt-Ctrl to scale selected; or change selection");
690 }
691 }
692
693 @Override
694 public boolean layerIsSupported(Layer l) {
695 return l instanceof OsmDataLayer;
696 }
697
698 private static void debug(String s) {
699 //System.err.println("SelectAction:" + s);
700 }
701}
Note: See TracBrowser for help on using the repository browser.