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

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

Sonar/FindBugs - Nested blocks of code should not be left empty

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