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

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

create virtual nodes only in case point is moved

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