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

Last change on this file since 4982 was 4982, checked in by stoecker, 12 years ago

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

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