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

Last change on this file since 5260 was 5260, checked in by bastiK, 12 years ago

bugfix: Alt Gr not working as expected on Unity / Ubuntu 11.10 64bit in ImproveWayAccuracyAction

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