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

Last change on this file since 6388 was 6388, checked in by simon04, 10 years ago

fix #9111 - fix wording/typos in English strings

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