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

Last change on this file since 1054 was 1054, checked in by stoecker, 16 years ago

fixed a lot of the shortcut related translations

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