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

Last change on this file since 7227 was 7227, checked in by akks, 10 years ago

show advanced preferences of A and W modes before enrtering them

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