source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java@ 8378

Last change on this file since 8378 was 8378, checked in by Don-vip, 9 years ago

fix copyright/license headers globally

  • Property svn:eol-style set to native
File size: 22.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Color;
9import java.awt.Cursor;
10import java.awt.Graphics2D;
11import java.awt.Point;
12import java.awt.Stroke;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.awt.geom.GeneralPath;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.LinkedList;
19import java.util.List;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.DeleteCommand;
28import org.openstreetmap.josm.command.MoveCommand;
29import org.openstreetmap.josm.command.SequenceCommand;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.SelectionChangedListener;
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.paint.PaintColors;
39import org.openstreetmap.josm.gui.MapFrame;
40import org.openstreetmap.josm.gui.MapView;
41import org.openstreetmap.josm.gui.layer.Layer;
42import org.openstreetmap.josm.gui.layer.MapViewPaintable;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.gui.util.GuiHelper;
45import org.openstreetmap.josm.gui.util.ModifierListener;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.openstreetmap.josm.tools.Pair;
48import org.openstreetmap.josm.tools.Shortcut;
49
50/**
51 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
52 */
53public class ImproveWayAccuracyAction extends MapMode implements MapViewPaintable,
54 SelectionChangedListener, ModifierListener {
55
56 enum State {
57 selecting, improving
58 }
59
60 private State state;
61
62 private MapView mv;
63
64 private static final long serialVersionUID = 42L;
65
66 private transient Way targetWay;
67 private transient Node candidateNode = null;
68 private transient WaySegment candidateSegment = null;
69
70 private Point mousePos = null;
71 private boolean dragging = false;
72
73 private final Cursor cursorSelect;
74 private final Cursor cursorSelectHover;
75 private final Cursor cursorImprove;
76 private final Cursor cursorImproveAdd;
77 private final Cursor cursorImproveDelete;
78 private final Cursor cursorImproveAddLock;
79 private final Cursor cursorImproveLock;
80
81 private Color guideColor;
82 private transient Stroke selectTargetWayStroke;
83 private transient Stroke moveNodeStroke;
84 private transient Stroke addNodeStroke;
85 private transient Stroke deleteNodeStroke;
86 private int dotSize;
87
88 private boolean selectionChangedBlocked = false;
89
90 protected String oldModeHelpText;
91
92 /**
93 * Constructs a new {@code ImproveWayAccuracyAction}.
94 * @param mapFrame Map frame
95 */
96 public ImproveWayAccuracyAction(MapFrame mapFrame) {
97 super(tr("Improve Way Accuracy"), "improvewayaccuracy",
98 tr("Improve Way Accuracy mode"),
99 Shortcut.registerShortcut("mapmode:ImproveWayAccuracy",
100 tr("Mode: {0}", tr("Improve Way Accuracy")),
101 KeyEvent.VK_W, Shortcut.DIRECT), mapFrame, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
102
103 cursorSelect = ImageProvider.getCursor("normal", "mode");
104 cursorSelectHover = ImageProvider.getCursor("hand", "mode");
105 cursorImprove = ImageProvider.getCursor("crosshair", null);
106 cursorImproveAdd = ImageProvider.getCursor("crosshair", "addnode");
107 cursorImproveDelete = ImageProvider.getCursor("crosshair", "delete_node");
108 cursorImproveAddLock = ImageProvider.getCursor("crosshair",
109 "add_node_lock");
110 cursorImproveLock = ImageProvider.getCursor("crosshair", "lock");
111 readPreferences();
112 }
113
114 // -------------------------------------------------------------------------
115 // Mode methods
116 // -------------------------------------------------------------------------
117 @Override
118 public void enterMode() {
119 if (!isEnabled()) {
120 return;
121 }
122 super.enterMode();
123 readPreferences();
124
125 mv = Main.map.mapView;
126 mousePos = null;
127 oldModeHelpText = "";
128
129 if (getCurrentDataSet() == null) {
130 return;
131 }
132
133 updateStateByCurrentSelection();
134
135 Main.map.mapView.addMouseListener(this);
136 Main.map.mapView.addMouseMotionListener(this);
137 Main.map.mapView.addTemporaryLayer(this);
138 DataSet.addSelectionListener(this);
139
140 Main.map.keyDetector.addModifierListener(this);
141 }
142
143 private void readPreferences() {
144 guideColor = Main.pref.getColor(marktr("improve way accuracy helper line"), null);
145 if (guideColor == null) guideColor = PaintColors.HIGHLIGHT.get();
146
147 selectTargetWayStroke = GuiHelper.getCustomizedStroke(Main.pref.get("improvewayaccuracy.stroke.select-target", "2"));
148 moveNodeStroke = GuiHelper.getCustomizedStroke(Main.pref.get("improvewayaccuracy.stroke.move-node", "1 6"));
149 addNodeStroke = GuiHelper.getCustomizedStroke(Main.pref.get("improvewayaccuracy.stroke.add-node", "1"));
150 deleteNodeStroke = GuiHelper.getCustomizedStroke(Main.pref.get("improvewayaccuracy.stroke.delete-node", "1"));
151 dotSize = Main.pref.getInteger("improvewayaccuracy.dot-size",6);
152 }
153
154 @Override
155 public void exitMode() {
156 super.exitMode();
157
158 Main.map.mapView.removeMouseListener(this);
159 Main.map.mapView.removeMouseMotionListener(this);
160 Main.map.mapView.removeTemporaryLayer(this);
161 DataSet.removeSelectionListener(this);
162
163 Main.map.keyDetector.removeModifierListener(this);
164 Main.map.mapView.repaint();
165 }
166
167 @Override
168 protected void updateStatusLine() {
169 String newModeHelpText = getModeHelpText();
170 if (!newModeHelpText.equals(oldModeHelpText)) {
171 oldModeHelpText = newModeHelpText;
172 Main.map.statusLine.setHelpText(newModeHelpText);
173 Main.map.statusLine.repaint();
174 }
175 }
176
177 @Override
178 public String getModeHelpText() {
179 if (state == State.selecting) {
180 if (targetWay != null) {
181 return tr("Click on the way to start improving its shape.");
182 } else {
183 return tr("Select a way that you want to make more accurate.");
184 }
185 } else {
186 if (ctrl) {
187 return tr("Click to add a new node. Release Ctrl to move existing nodes or hold Alt to delete.");
188 } else if (alt) {
189 return tr("Click to delete the highlighted node. Release Alt to move existing nodes or hold Ctrl to add new nodes.");
190 } else {
191 return tr("Click to move the highlighted node. Hold Ctrl to add new nodes, or Alt to delete.");
192 }
193 }
194 }
195
196 @Override
197 public boolean layerIsSupported(Layer l) {
198 return l instanceof OsmDataLayer;
199 }
200
201 @Override
202 protected void updateEnabledState() {
203 setEnabled(getEditLayer() != null);
204 }
205
206 // -------------------------------------------------------------------------
207 // MapViewPaintable methods
208 // -------------------------------------------------------------------------
209 /**
210 * Redraws temporary layer. Highlights targetWay in select mode. Draws
211 * preview lines in improve mode and highlights the candidateNode
212 */
213 @Override
214 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
215 if (mousePos == null) {
216 return;
217 }
218
219 g.setColor(guideColor);
220
221 if (state == State.selecting && targetWay != null) {
222 // Highlighting the targetWay in Selecting state
223 // Non-native highlighting is used, because sometimes highlighted
224 // segments are covered with others, which is bad.
225 g.setStroke(selectTargetWayStroke);
226
227 List<Node> nodes = targetWay.getNodes();
228
229 GeneralPath b = new GeneralPath();
230 Point p0 = mv.getPoint(nodes.get(0));
231 Point pn;
232 b.moveTo(p0.x, p0.y);
233
234 for (Node n : nodes) {
235 pn = mv.getPoint(n);
236 b.lineTo(pn.x, pn.y);
237 }
238 if (targetWay.isClosed()) {
239 b.lineTo(p0.x, p0.y);
240 }
241
242 g.draw(b);
243
244 } else if (state == State.improving) {
245 // Drawing preview lines and highlighting the node
246 // that is going to be moved.
247 // Non-native highlighting is used here as well.
248
249 // Finding endpoints
250 Point p1 = null, p2 = null;
251 if (ctrl && candidateSegment != null) {
252 g.setStroke(addNodeStroke);
253 p1 = mv.getPoint(candidateSegment.getFirstNode());
254 p2 = mv.getPoint(candidateSegment.getSecondNode());
255 } else if (!alt && !ctrl && candidateNode != null) {
256 g.setStroke(moveNodeStroke);
257 List<Pair<Node, Node>> wpps = targetWay.getNodePairs(false);
258 for (Pair<Node, Node> wpp : wpps) {
259 if (wpp.a == candidateNode) {
260 p1 = mv.getPoint(wpp.b);
261 }
262 if (wpp.b == candidateNode) {
263 p2 = mv.getPoint(wpp.a);
264 }
265 if (p1 != null && p2 != null) {
266 break;
267 }
268 }
269 } else if (alt && !ctrl && candidateNode != null) {
270 g.setStroke(deleteNodeStroke);
271 List<Node> nodes = targetWay.getNodes();
272 int index = nodes.indexOf(candidateNode);
273
274 // Only draw line if node is not first and/or last
275 if (index != 0 && index != (nodes.size() - 1)) {
276 p1 = mv.getPoint(nodes.get(index - 1));
277 p2 = mv.getPoint(nodes.get(index + 1));
278 }
279 // TODO: indicate what part that will be deleted? (for end nodes)
280 }
281
282
283 // Drawing preview lines
284 GeneralPath b = new GeneralPath();
285 if (alt && !ctrl) {
286 // In delete mode
287 if (p1 != null && p2 != null) {
288 b.moveTo(p1.x, p1.y);
289 b.lineTo(p2.x, p2.y);
290 }
291 } else {
292 // In add or move mode
293 if (p1 != null) {
294 b.moveTo(mousePos.x, mousePos.y);
295 b.lineTo(p1.x, p1.y);
296 }
297 if (p2 != null) {
298 b.moveTo(mousePos.x, mousePos.y);
299 b.lineTo(p2.x, p2.y);
300 }
301 }
302 g.draw(b);
303
304 // Highlighting candidateNode
305 if (candidateNode != null) {
306 p1 = mv.getPoint(candidateNode);
307 g.fillRect(p1.x - dotSize/2, p1.y - dotSize/2, dotSize, dotSize);
308 }
309
310 }
311 }
312
313 // -------------------------------------------------------------------------
314 // Event handlers
315 // -------------------------------------------------------------------------
316 @Override
317 public void modifiersChanged(int modifiers) {
318 if (!Main.isDisplayingMapView() || !Main.map.mapView.isActiveLayerDrawable()) {
319 return;
320 }
321 updateKeyModifiers(modifiers);
322 updateCursorDependentObjectsIfNeeded();
323 updateCursor();
324 updateStatusLine();
325 Main.map.mapView.repaint();
326 }
327
328 @Override
329 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
330 if (selectionChangedBlocked) {
331 return;
332 }
333 updateStateByCurrentSelection();
334 }
335
336 @Override
337 public void mouseDragged(MouseEvent e) {
338 dragging = true;
339 mouseMoved(e);
340 }
341
342 @Override
343 public void mouseMoved(MouseEvent e) {
344 if (!isEnabled()) {
345 return;
346 }
347
348 mousePos = e.getPoint();
349
350 updateKeyModifiers(e);
351 updateCursorDependentObjectsIfNeeded();
352 updateCursor();
353 updateStatusLine();
354 Main.map.mapView.repaint();
355 }
356
357 @Override
358 public void mouseReleased(MouseEvent e) {
359 dragging = false;
360 if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) {
361 return;
362 }
363
364 updateKeyModifiers(e);
365 mousePos = e.getPoint();
366
367 if (state == State.selecting) {
368 if (targetWay != null) {
369 getCurrentDataSet().setSelected(targetWay.getPrimitiveId());
370 updateStateByCurrentSelection();
371 }
372 } else if (state == State.improving && mousePos != null) {
373 // Checking if the new coordinate is outside of the world
374 if (mv.getLatLon(mousePos.x, mousePos.y).isOutSideWorld()) {
375 JOptionPane.showMessageDialog(Main.parent,
376 tr("Cannot place a node outside of the world."),
377 tr("Warning"), JOptionPane.WARNING_MESSAGE);
378 return;
379 }
380
381 if (ctrl && !alt && candidateSegment != null) {
382 // Adding a new node to the highlighted segment
383 // Important: If there are other ways containing the same
384 // segment, a node must added to all of that ways.
385 Collection<Command> virtualCmds = new LinkedList<>();
386
387 // Creating a new node
388 Node virtualNode = new Node(mv.getEastNorth(mousePos.x,
389 mousePos.y));
390 virtualCmds.add(new AddCommand(virtualNode));
391
392 // Looking for candidateSegment copies in ways that are
393 // referenced
394 // by candidateSegment nodes
395 List<Way> firstNodeWays = OsmPrimitive.getFilteredList(
396 candidateSegment.getFirstNode().getReferrers(),
397 Way.class);
398 List<Way> secondNodeWays = OsmPrimitive.getFilteredList(
399 candidateSegment.getFirstNode().getReferrers(),
400 Way.class);
401
402 Collection<WaySegment> virtualSegments = new LinkedList<>();
403 for (Way w : firstNodeWays) {
404 List<Pair<Node, Node>> wpps = w.getNodePairs(true);
405 for (Way w2 : secondNodeWays) {
406 if (!w.equals(w2)) {
407 continue;
408 }
409 // A way is referenced in both nodes.
410 // Checking if there is such segment
411 int i = -1;
412 for (Pair<Node, Node> wpp : wpps) {
413 ++i;
414 boolean ab = wpp.a.equals(candidateSegment.getFirstNode())
415 && wpp.b.equals(candidateSegment.getSecondNode());
416 boolean ba = wpp.b.equals(candidateSegment.getFirstNode())
417 && wpp.a.equals(candidateSegment.getSecondNode());
418 if (ab || ba) {
419 virtualSegments.add(new WaySegment(w, i));
420 }
421 }
422 }
423 }
424
425 // Adding the node to all segments found
426 for (WaySegment virtualSegment : virtualSegments) {
427 Way w = virtualSegment.way;
428 Way wnew = new Way(w);
429 wnew.addNode(virtualSegment.lowerIndex + 1, virtualNode);
430 virtualCmds.add(new ChangeCommand(w, wnew));
431 }
432
433 // Finishing the sequence command
434 String text = trn("Add a new node to way",
435 "Add a new node to {0} ways",
436 virtualSegments.size(), virtualSegments.size());
437
438 Main.main.undoRedo.add(new SequenceCommand(text, virtualCmds));
439
440 } else if (alt && !ctrl && candidateNode != null) {
441 // Deleting the highlighted node
442
443 //check to see if node is in use by more than one object
444 List<OsmPrimitive> referrers = candidateNode.getReferrers();
445 List<Way> ways = OsmPrimitive.getFilteredList(referrers, Way.class);
446 if (referrers.size() != 1 || ways.size() != 1) {
447 JOptionPane.showMessageDialog(Main.parent,
448 tr("Cannot delete node that is referenced by multiple objects"),
449 tr("Error"), JOptionPane.ERROR_MESSAGE);
450 } else if (candidateNode.isTagged()) {
451 JOptionPane.showMessageDialog(Main.parent,
452 tr("Cannot delete node that has tags"),
453 tr("Error"), JOptionPane.ERROR_MESSAGE);
454 } else {
455 List<Node> nodeList = new ArrayList<>();
456 nodeList.add(candidateNode);
457 Command deleteCmd = DeleteCommand.delete(getEditLayer(), nodeList, true);
458 if (deleteCmd != null) {
459 Main.main.undoRedo.add(deleteCmd);
460 }
461 }
462
463
464 } else if (candidateNode != null) {
465 // Moving the highlighted node
466 EastNorth nodeEN = candidateNode.getEastNorth();
467 EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y);
468
469 Main.main.undoRedo.add(new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north()
470 - nodeEN.north()));
471 }
472 }
473
474 mousePos = null;
475 updateCursor();
476 updateStatusLine();
477 Main.map.mapView.repaint();
478 }
479
480 @Override
481 public void mouseExited(MouseEvent e) {
482 if (!isEnabled()) {
483 return;
484 }
485
486 if (!dragging) {
487 mousePos = null;
488 }
489 Main.map.mapView.repaint();
490 }
491
492 // -------------------------------------------------------------------------
493 // Custom methods
494 // -------------------------------------------------------------------------
495 /**
496 * Sets new cursor depending on state, mouse position
497 */
498 private void updateCursor() {
499 if (!isEnabled()) {
500 mv.setNewCursor(null, this);
501 return;
502 }
503
504 if (state == State.selecting) {
505 mv.setNewCursor(targetWay == null ? cursorSelect
506 : cursorSelectHover, this);
507 } else if (state == State.improving) {
508 if (alt && !ctrl) {
509 mv.setNewCursor(cursorImproveDelete, this);
510 } else if (shift || dragging) {
511 if (ctrl) {
512 mv.setNewCursor(cursorImproveAddLock, this);
513 } else {
514 mv.setNewCursor(cursorImproveLock, this);
515 }
516 } else if (ctrl && !alt) {
517 mv.setNewCursor(cursorImproveAdd, this);
518 } else {
519 mv.setNewCursor(cursorImprove, this);
520 }
521 }
522 }
523
524 /**
525 * Updates these objects under cursor: targetWay, candidateNode,
526 * candidateSegment
527 */
528 public void updateCursorDependentObjectsIfNeeded() {
529 if (state == State.improving && (shift || dragging)
530 && !(candidateNode == null && candidateSegment == null)) {
531 return;
532 }
533
534 if (mousePos == null) {
535 candidateNode = null;
536 candidateSegment = null;
537 return;
538 }
539
540 if (state == State.selecting) {
541 targetWay = ImproveWayAccuracyHelper.findWay(mv, mousePos);
542 } else if (state == State.improving) {
543 if (ctrl && !alt) {
544 candidateSegment = ImproveWayAccuracyHelper.findCandidateSegment(mv,
545 targetWay, mousePos);
546 candidateNode = null;
547 } else {
548 candidateNode = ImproveWayAccuracyHelper.findCandidateNode(mv,
549 targetWay, mousePos);
550 candidateSegment = null;
551 }
552 }
553 }
554
555 /**
556 * Switches to Selecting state
557 */
558 public void startSelecting() {
559 state = State.selecting;
560
561 targetWay = null;
562
563 mv.repaint();
564 updateStatusLine();
565 }
566
567 /**
568 * Switches to Improving state
569 *
570 * @param targetWay Way that is going to be improved
571 */
572 public void startImproving(Way targetWay) {
573 state = State.improving;
574
575 Collection<OsmPrimitive> currentSelection = getCurrentDataSet().getSelected();
576 if (currentSelection.size() != 1
577 || !currentSelection.iterator().next().equals(targetWay)) {
578 selectionChangedBlocked = true;
579 getCurrentDataSet().clearSelection();
580 getCurrentDataSet().setSelected(targetWay.getPrimitiveId());
581 selectionChangedBlocked = false;
582 }
583
584 this.targetWay = targetWay;
585 this.candidateNode = null;
586 this.candidateSegment = null;
587
588 mv.repaint();
589 updateStatusLine();
590 }
591
592 /**
593 * Updates the state according to the current selection. Goes to Improve
594 * state if a single way or node is selected. Extracts a way by a node in
595 * the second case.
596 *
597 */
598 private void updateStateByCurrentSelection() {
599 final List<Node> nodeList = new ArrayList<>();
600 final List<Way> wayList = new ArrayList<>();
601 final Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
602
603 // Collecting nodes and ways from the selection
604 for (OsmPrimitive p : sel) {
605 if (p instanceof Way) {
606 wayList.add((Way) p);
607 }
608 if (p instanceof Node) {
609 nodeList.add((Node) p);
610 }
611 }
612
613 if (wayList.size() == 1) {
614 // Starting improving the single selected way
615 startImproving(wayList.get(0));
616 return;
617 } else if (nodeList.size() == 1) {
618 // Starting improving the only way of the single selected node
619 List<OsmPrimitive> r = nodeList.get(0).getReferrers();
620 if (r.size() == 1 && (r.get(0) instanceof Way)) {
621 startImproving((Way) r.get(0));
622 return;
623 }
624 }
625
626 // Starting selecting by default
627 startSelecting();
628 }
629}
Note: See TracBrowser for help on using the repository browser.