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

Last change on this file since 3775 was 3754, checked in by stoecker, 13 years ago

some cleanups in help page marking

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