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

Last change on this file since 3702 was 3702, checked in by bastiK, 13 years ago

applied #5652 (patch by Olivier Croquette) - add support for scaling objects

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