source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java@ 5741

Last change on this file since 5741 was 5741, checked in by akks, 11 years ago

Extrude mode refactoring (func. splitting, javadoc). Use constant directions for Ctrl-Drag.
See #8447: Add color and stroke customization to Extrude mode
fix stroke=1 0 0 exception (minor)

  • Property svn:eol-style set to native
File size: 31.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.AWTEvent;
9import java.awt.BasicStroke;
10import java.awt.Color;
11import java.awt.Cursor;
12import java.awt.Graphics2D;
13import java.awt.Point;
14import java.awt.Rectangle;
15import java.awt.Stroke;
16import java.awt.Toolkit;
17import java.awt.event.AWTEventListener;
18import java.awt.event.ActionEvent;
19import java.awt.event.InputEvent;
20import java.awt.event.KeyEvent;
21import java.awt.event.MouseEvent;
22import java.awt.geom.AffineTransform;
23import java.awt.geom.GeneralPath;
24import java.awt.geom.Line2D;
25import java.awt.geom.NoninvertibleTransformException;
26import java.awt.geom.Point2D;
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.LinkedList;
30import java.util.List;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.command.AddCommand;
34import org.openstreetmap.josm.command.ChangeCommand;
35import org.openstreetmap.josm.command.Command;
36import org.openstreetmap.josm.command.MoveCommand;
37import org.openstreetmap.josm.command.SequenceCommand;
38import org.openstreetmap.josm.data.Bounds;
39import org.openstreetmap.josm.data.coor.EastNorth;
40import org.openstreetmap.josm.data.osm.Node;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.Way;
43import org.openstreetmap.josm.data.osm.WaySegment;
44import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
45import org.openstreetmap.josm.gui.MapFrame;
46import org.openstreetmap.josm.gui.MapView;
47import org.openstreetmap.josm.gui.layer.Layer;
48import org.openstreetmap.josm.gui.layer.MapViewPaintable;
49import org.openstreetmap.josm.gui.layer.OsmDataLayer;
50import org.openstreetmap.josm.gui.util.GuiHelper;
51import org.openstreetmap.josm.tools.Geometry;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.Shortcut;
54
55/**
56 * Makes a rectangle from a line, or modifies a rectangle.
57 */
58public class ExtrudeAction extends MapMode implements MapViewPaintable {
59
60 enum Mode { extrude, translate, select, create_new }
61
62 private Mode mode = Mode.select;
63
64 /**
65 * If true, when extruding create new node even if segments parallel.
66 */
67 private boolean alwaysCreateNodes = false;
68
69 private long mouseDownTime = 0;
70 private WaySegment selectedSegment = null;
71 private Color mainColor;
72 private Stroke mainStroke;
73
74 /**
75 * drawing settings for helper lines
76 */
77 private Color helperColor;
78 private Stroke helperStrokeDash;
79 private Stroke helperStrokeRA;
80
81 private Stroke oldLineStroke;
82 private double symbolSize;
83 /**
84 * Possible directions to move to.
85 */
86 private List<ReferenceSegment> possibleMoveDirections;
87
88 /**
89 * The direction that is currently active.
90 */
91 private ReferenceSegment activeMoveDirection;
92
93 /**
94 * The position of the mouse cursor when the drag action was initiated.
95 */
96 private Point initialMousePos;
97 /**
98 * The time which needs to pass between click and release before something
99 * counts as a move, in milliseconds
100 */
101 private int initialMoveDelay = 200;
102 /**
103 * The initial EastNorths of node1 and node2
104 */
105 private EastNorth initialN1en;
106 private EastNorth initialN2en;
107 /**
108 * The new EastNorths of node1 and node2
109 */
110 private EastNorth newN1en;
111 private EastNorth newN2en;
112
113 /**
114 * the command that performed last move.
115 */
116 private MoveCommand moveCommand;
117
118 /** The cursor for the 'create_new' mode. */
119 private final Cursor cursorCreateNew;
120
121 /** The cursor for the 'translate' mode. */
122 private final Cursor cursorTranslate;
123
124 /** The cursor for the 'alwaysCreateNodes' submode. */
125 private final Cursor cursorCreateNodes;
126
127 private class ReferenceSegment {
128 public final EastNorth en;
129 public final EastNorth p1;
130 public final EastNorth p2;
131 public final boolean perpendicular;
132
133 public ReferenceSegment(EastNorth en, EastNorth p1, EastNorth p2, boolean perpendicular) {
134 this.en = en;
135 this.p1 = p1;
136 this.p2 = p2;
137 this.perpendicular = perpendicular;
138 }
139 }
140
141 /**
142 * This listener is used to indicate the 'create_new' mode, if the Alt modifier is pressed.
143 */
144 private final AWTEventListener altKeyListener = new AWTEventListener() {
145 @Override
146 public void eventDispatched(AWTEvent e) {
147 if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable())
148 return;
149 InputEvent ie = (InputEvent) e;
150 boolean alt = (ie.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
151 boolean ctrl = (ie.getModifiers() & (ActionEvent.CTRL_MASK)) != 0;
152 boolean shift = (ie.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0;
153 if (mode == Mode.select) {
154 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this);
155 }
156 }
157 };
158
159 /**
160 * Create a new SelectAction
161 * @param mapFrame The MapFrame this action belongs to.
162 */
163 public ExtrudeAction(MapFrame mapFrame) {
164 super(tr("Extrude"), "extrude/extrude", tr("Create areas"),
165 Shortcut.registerShortcut("mapmode:extrude", tr("Mode: {0}", tr("Extrude")), KeyEvent.VK_X, Shortcut.DIRECT),
166 mapFrame,
167 ImageProvider.getCursor("normal", "rectangle"));
168 putValue("help", ht("/Action/Extrude"));
169 cursorCreateNew = ImageProvider.getCursor("normal", "rectangle_plus");
170 cursorTranslate = ImageProvider.getCursor("normal", "rectangle_move");
171 cursorCreateNodes = ImageProvider.getCursor("normal", "rectangle_plussmall");
172 }
173
174 @Override public String getModeHelpText() {
175 if (mode == Mode.translate)
176 return tr("Move a segment along its normal, then release the mouse button.");
177 else if (mode == Mode.extrude)
178 return tr("Draw a rectangle of the desired size, then release the mouse button.");
179 else if (mode == Mode.create_new)
180 return tr("Draw a rectangle of the desired size, then release the mouse button.");
181 else
182 return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " +
183 "Alt-drag to create a new rectangle, double click to add a new node.");
184 }
185
186 @Override public boolean layerIsSupported(Layer l) {
187 return l instanceof OsmDataLayer;
188 }
189
190 @Override public void enterMode() {
191 super.enterMode();
192 Main.map.mapView.addMouseListener(this);
193 Main.map.mapView.addMouseMotionListener(this);
194 try {
195 Toolkit.getDefaultToolkit().addAWTEventListener(altKeyListener, AWTEvent.KEY_EVENT_MASK);
196 } catch (SecurityException ex) {
197 }
198 initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
199 mainColor = Main.pref.getColor(marktr("Extrude: main line"), null);
200 if (mainColor == null) mainColor = PaintColors.SELECTED.get();
201 helperColor = Main.pref.getColor(marktr("Extrude: helper line"), Color.ORANGE);
202 helperStrokeDash = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.helper-line", "1 4"));
203 helperStrokeRA = new BasicStroke(1);
204 symbolSize = Main.pref.getDouble("extrude.angle-symbol-radius", 8);
205
206 oldLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.ctrl.stroke.old-line", "1"));
207 mainStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.main", "3"));
208 }
209
210 @Override public void exitMode() {
211 Main.map.mapView.removeMouseListener(this);
212 Main.map.mapView.removeMouseMotionListener(this);
213 Main.map.mapView.removeTemporaryLayer(this);
214 try {
215 Toolkit.getDefaultToolkit().removeAWTEventListener(altKeyListener);
216 } catch (SecurityException ex) {
217 }
218 super.exitMode();
219 }
220
221 /**
222 * If the left mouse button is pressed over a segment, switch
223 * to either extrude, translate or create_new mode depending on whether Ctrl or Alt is held.
224 */
225 @Override public void mousePressed(MouseEvent e) {
226 if(!Main.map.mapView.isActiveLayerVisible())
227 return;
228 if (!(Boolean)this.getValue("active"))
229 return;
230 if (e.getButton() != MouseEvent.BUTTON1)
231 return;
232
233 updateKeyModifiers(e);
234
235 Node nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
236
237 selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
238 boolean dragNode = nearestNode!=null;
239
240 if (selectedSegment == null && nearestNode == null) {
241 // If nothing gets caught, stay in select mode
242 } else {
243 // Otherwise switch to another mode
244
245 if (ctrl) {
246 mode = Mode.translate;
247 } else if (alt) {
248 mode = Mode.create_new;
249 // create a new segment and then select and extrude the new segment
250 getCurrentDataSet().setSelected(selectedSegment.way);
251 alwaysCreateNodes = true;
252 } else {
253 mode = Mode.extrude;
254 getCurrentDataSet().setSelected(selectedSegment.way);
255 alwaysCreateNodes = shift;
256 }
257
258
259 calculatePossibleDirections();
260
261 // Signifies that nothing has happened yet
262 newN1en = null;
263 newN2en = null;
264 moveCommand = null;
265
266 Main.map.mapView.addTemporaryLayer(this);
267
268 updateStatusLine();
269 Main.map.mapView.repaint();
270
271 // Make note of time pressed
272 mouseDownTime = System.currentTimeMillis();
273
274 // Make note of mouse position
275 initialMousePos = e.getPoint();
276 }
277 }
278
279 /**
280 * Perform action depending on what mode we're in.
281 */
282 @Override public void mouseDragged(MouseEvent e) {
283 if(!Main.map.mapView.isActiveLayerVisible())
284 return;
285
286 // do not count anything as a drag if it lasts less than 100 milliseconds.
287 if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)
288 return;
289
290 if (mode == Mode.select) {
291 // Just sit tight and wait for mouse to be released.
292 } else {
293 //move, create new and extrude mode - move the selected segment
294
295 EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
296 EastNorth bestMovement = calculateBestMovement(mouseEn);
297
298 newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
299 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
300
301 // find out the movement distance, in metres
302 double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(newN1en));
303 Main.map.statusLine.setDist(distance);
304 updateStatusLine();
305
306 Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
307
308 if (mode == Mode.extrude || mode == Mode.create_new) {
309 //nothing here
310 } else if (mode == Mode.translate) {
311 //move nodes to new position
312 if (moveCommand == null) {
313 //make a new move command
314 Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
315 nodelist.add(selectedSegment.getFirstNode());
316 nodelist.add(selectedSegment.getSecondNode());
317 moveCommand = new MoveCommand(nodelist, bestMovement.getX(), bestMovement.getY());
318 Main.main.undoRedo.add(moveCommand);
319 } else {
320 //reuse existing move command
321 moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY());
322 }
323 }
324
325 Main.map.mapView.repaint();
326 }
327 }
328
329 /**
330 * Do anything that needs to be done, then switch back to select mode
331 */
332 @Override public void mouseReleased(MouseEvent e) {
333
334 if(!Main.map.mapView.isActiveLayerVisible())
335 return;
336
337 if (mode == Mode.select) {
338 // Nothing to be done
339 } else {
340 if (mode == Mode.create_new) {
341 if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
342 // crete a new rectangle
343 Collection<Command> cmds = new LinkedList<Command>();
344 Node third = new Node(newN2en);
345 Node fourth = new Node(newN1en);
346 Way wnew = new Way();
347 wnew.addNode(selectedSegment.getFirstNode());
348 wnew.addNode(selectedSegment.getSecondNode());
349 wnew.addNode(third);
350 wnew.addNode(fourth);
351 // ... and close the way
352 wnew.addNode(selectedSegment.getFirstNode());
353 // undo support
354 cmds.add(new AddCommand(third));
355 cmds.add(new AddCommand(fourth));
356 cmds.add(new AddCommand(wnew));
357 Command c = new SequenceCommand(tr("Extrude Way"), cmds);
358 Main.main.undoRedo.add(c);
359 getCurrentDataSet().setSelected(wnew);
360 }
361 } else if (mode == Mode.extrude) {
362 if( e.getClickCount() == 2 && e.getPoint().equals(initialMousePos) ) {
363 // double click add a new node
364 // Should maybe do the same as in DrawAction and fetch all nearby segments?
365 WaySegment ws = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
366 if (ws != null) {
367 Node n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
368 EastNorth A = ws.getFirstNode().getEastNorth();
369 EastNorth B = ws.getSecondNode().getEastNorth();
370 n.setEastNorth(Geometry.closestPointToSegment(A, B, n.getEastNorth()));
371 Way wnew = new Way(ws.way);
372 wnew.addNode(ws.lowerIndex+1, n);
373 SequenceCommand cmds = new SequenceCommand(tr("Add a new node to an existing way"),
374 new AddCommand(n), new ChangeCommand(ws.way, wnew));
375 Main.main.undoRedo.add(cmds);
376 }
377 }
378 else if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null && selectedSegment != null) {
379 // create extrusion
380
381 Collection<Command> cmds = new LinkedList<Command>();
382 Way wnew = new Way(selectedSegment.way);
383 int insertionPoint = selectedSegment.lowerIndex + 1;
384
385 //find if the new points overlap existing segments (in case of 90 degree angles)
386 Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
387 boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParallel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en);
388 boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);
389
390 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
391 //move existing node
392 Node n1Old = selectedSegment.getFirstNode();
393 cmds.add(new MoveCommand(n1Old, Main.getProjection().eastNorth2latlon(newN1en)));
394 } else {
395 //introduce new node
396 Node n1New = new Node(Main.getProjection().eastNorth2latlon(newN1en));
397 wnew.addNode(insertionPoint, n1New);
398 insertionPoint ++;
399 cmds.add(new AddCommand(n1New));
400 }
401
402 //find if the new points overlap existing segments (in case of 90 degree angles)
403 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
404 nodeOverlapsSegment = nextNode != null && Geometry.segmentsParallel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en);
405 hasOtherWays = hasNodeOtherWays(selectedSegment.getSecondNode(), selectedSegment.way);
406
407 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
408 //move existing node
409 Node n2Old = selectedSegment.getSecondNode();
410 cmds.add(new MoveCommand(n2Old, Main.getProjection().eastNorth2latlon(newN2en)));
411 } else {
412 //introduce new node
413 Node n2New = new Node(Main.getProjection().eastNorth2latlon(newN2en));
414 wnew.addNode(insertionPoint, n2New);
415 insertionPoint ++;
416 cmds.add(new AddCommand(n2New));
417 }
418
419 //the way was a single segment, close the way
420 if (wnew.getNodesCount() == 4) {
421 wnew.addNode(selectedSegment.getFirstNode());
422 }
423
424 cmds.add(new ChangeCommand(selectedSegment.way, wnew));
425 Command c = new SequenceCommand(tr("Extrude Way"), cmds);
426 Main.main.undoRedo.add(c);
427 }
428 } else if (mode == Mode.translate) {
429 //Commit translate
430 //the move command is already committed in mouseDragged
431 }
432
433 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
434 boolean ctrl = (e.getModifiers() & (ActionEvent.CTRL_MASK)) != 0;
435 boolean shift = (e.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0;
436 // Switch back into select mode
437 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this);
438 Main.map.mapView.removeTemporaryLayer(this);
439 selectedSegment = null;
440 moveCommand = null;
441 mode = Mode.select;
442
443 updateStatusLine();
444 Main.map.mapView.repaint();
445 }
446 }
447
448 /**
449 * This method tests if a node has other ways apart from the given one.
450 * @param node
451 * @param myWay
452 * @return true of node belongs only to myWay, false if there are more ways.
453 */
454 private boolean hasNodeOtherWays(Node node, Way myWay) {
455 for (OsmPrimitive p : node.getReferrers()) {
456 if (p instanceof Way && p.isUsable() && p != myWay)
457 return true;
458 }
459 return false;
460 }
461
462 /**
463 * Determine best movenemnt from initialMousePos to current position @param mouseEn,
464 * choosing one of the directions @field possibleMoveDirections
465 * @return movement vector
466 */
467 private EastNorth calculateBestMovement(EastNorth mouseEn) {
468
469 EastNorth initialMouseEn = Main.map.mapView.getEastNorth(initialMousePos.x, initialMousePos.y);
470 EastNorth mouseMovement = new EastNorth(mouseEn.getX() - initialMouseEn.getX(), mouseEn.getY() - initialMouseEn.getY());
471
472 double bestDistance = Double.POSITIVE_INFINITY;
473 EastNorth bestMovement = null;
474 activeMoveDirection = null;
475
476 //find the best movement direction and vector
477 for (ReferenceSegment direction : possibleMoveDirections) {
478 EastNorth movement = calculateSegmentOffset(initialN1en, initialN2en, direction.en, mouseEn);
479 if (movement == null) {
480 //if direction parallel to segment.
481 continue;
482 }
483
484 double distanceFromMouseMovement = movement.distance(mouseMovement);
485 if (bestDistance > distanceFromMouseMovement) {
486 bestDistance = distanceFromMouseMovement;
487 activeMoveDirection = direction;
488 bestMovement = movement;
489 }
490 }
491 return bestMovement;
492 }
493
494 /***
495 * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position.
496 * @param segmentP1
497 * @param segmentP2
498 * @param targetPos
499 * @return offset amount of P1 and P2.
500 */
501 private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2, EastNorth moveDirection,
502 EastNorth targetPos) {
503 EastNorth intersectionPoint = Geometry.getLineLineIntersection(segmentP1, segmentP2, targetPos,
504 new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY()));
505
506 if (intersectionPoint == null)
507 return null;
508 else
509 //return distance form base to target position
510 return new EastNorth(targetPos.getX() - intersectionPoint.getX(),
511 targetPos.getY() - intersectionPoint.getY());
512 }
513
514 /**
515 * Gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments
516 */
517 private void calculatePossibleDirections() {
518 // remember initial positions for segment nodes.
519 initialN1en = selectedSegment.getFirstNode().getEastNorth();
520 initialN2en = selectedSegment.getSecondNode().getEastNorth();
521
522 //add direction perpendicular to the selected segment
523 possibleMoveDirections = new ArrayList<ReferenceSegment>();
524 possibleMoveDirections.add(new ReferenceSegment(new EastNorth(
525 initialN1en.getY() - initialN2en.getY(),
526 initialN2en.getX() - initialN1en.getX()
527 ), initialN1en, initialN2en, true));
528
529
530 //add directions parallel to neighbor segments
531 Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
532 if (prevNode != null) {
533 EastNorth en = prevNode.getEastNorth();
534 possibleMoveDirections.add(new ReferenceSegment(new EastNorth(
535 initialN1en.getX() - en.getX(),
536 initialN1en.getY() - en.getY()
537 ), initialN1en, en, false));
538 }
539
540 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
541 if (nextNode != null) {
542 EastNorth en = nextNode.getEastNorth();
543 possibleMoveDirections.add(new ReferenceSegment(new EastNorth(
544 initialN2en.getX() - en.getX(),
545 initialN2en.getY() - en.getY()
546 ), initialN2en, en, false));
547 }
548 }
549
550 /**
551 * Gets a node from selected way before given index.
552 * @param index index of current node
553 * @return index of previous node or -1 if there are no nodes there.
554 */
555 private int getPreviousNodeIndex(int index) {
556 if (index > 0)
557 return index - 1;
558 else if (selectedSegment.way.isClosed())
559 return selectedSegment.way.getNodesCount() - 2;
560 else
561 return -1;
562 }
563
564 /**
565 * Gets a node from selected way before given index.
566 * @param index index of current node
567 * @return previous node or null if there are no nodes there.
568 */
569 private Node getPreviousNode(int index) {
570 int indexPrev = getPreviousNodeIndex(index);
571 if (indexPrev >= 0)
572 return selectedSegment.way.getNode(indexPrev);
573 else
574 return null;
575 }
576
577
578 /**
579 * Gets a node from selected way after given index.
580 * @param index index of current node
581 * @return index of next node or -1 if there are no nodes there.
582 */
583 private int getNextNodeIndex(int index) {
584 int count = selectedSegment.way.getNodesCount();
585 if (index < count - 1)
586 return index + 1;
587 else if (selectedSegment.way.isClosed())
588 return 1;
589 else
590 return -1;
591 }
592
593 /**
594 * Gets a node from selected way after given index.
595 * @param index index of current node
596 * @return next node or null if there are no nodes there.
597 */
598 private Node getNextNode(int index) {
599 int indexNext = getNextNodeIndex(index);
600 if (indexNext >= 0)
601 return selectedSegment.way.getNode(indexNext);
602 else
603 return null;
604 }
605
606 public void paint(Graphics2D g, MapView mv, Bounds box) {
607 Graphics2D g2 = g;
608 if (mode == Mode.select) {
609 // Nothing to do
610 } else {
611 if (newN1en != null) {
612
613 Point p1 = mv.getPoint(initialN1en);
614 Point p2 = mv.getPoint(initialN2en);
615 Point p3 = mv.getPoint(newN1en);
616 Point p4 = mv.getPoint(newN2en);
617
618 EastNorth normalUnitVector = getNormalUniVector();
619
620 if (mode == Mode.extrude || mode == Mode.create_new) {
621 g2.setColor(mainColor);
622 g2.setStroke(mainStroke);
623 // Draw rectangle around new area.
624 GeneralPath b = new GeneralPath();
625 b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y);
626 b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
627 b.lineTo(p1.x, p1.y);
628 g2.draw(b);
629
630 if (activeMoveDirection != null) {
631 // Draw reference way
632 Point pr1 = mv.getPoint(activeMoveDirection.p1);
633 Point pr2 = mv.getPoint(activeMoveDirection.p2);
634 b = new GeneralPath();
635 b.moveTo(pr1.x, pr1.y);
636 b.lineTo(pr2.x, pr2.y);
637 g2.setColor(helperColor);
638 g2.setStroke(helperStrokeDash);
639 g2.draw(b);
640
641 // Draw right angle marker on first node position, only when moving at right angle
642 if (activeMoveDirection.perpendicular) {
643 // mirror RightAngle marker, so it is inside the extrude
644 double headingRefWS = activeMoveDirection.p1.heading(activeMoveDirection.p2);
645 double headingMoveDir = Math.atan2(normalUnitVector.getY(), normalUnitVector.getX());
646 double headingDiff = headingRefWS - headingMoveDir;
647 if (headingDiff < 0) headingDiff += 2 * Math.PI;
648 boolean mirrorRA = Math.abs(headingDiff - Math.PI) > 1e-5;
649 drawAngleSymbol(g2, pr1, normalUnitVector, mirrorRA);
650 }
651 }
652 } else if (mode == Mode.translate) {
653 g2.setColor(mainColor);
654 g2.setStroke(oldLineStroke);
655 // Highlight the old segment
656 g2.setStroke(mainStroke);
657 Line2D oldline = new Line2D.Double(p1, p2);
658 g2.draw(oldline);
659
660 if (activeMoveDirection != null) {
661
662 g2.setColor(helperColor);
663 g2.setStroke(helperStrokeDash);
664 // Draw a guideline along the normal.
665 Line2D normline;
666 Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
667 normline = createSemiInfiniteLine(centerpoint, normalUnitVector, g2);
668 g2.draw(normline);
669 // Draw right angle marker on initial position, only when moving at right angle
670 if (activeMoveDirection.perpendicular) {
671 // EastNorth units per pixel
672 g2.setStroke(helperStrokeRA);
673 g2.setColor(mainColor);
674 drawAngleSymbol(g2, centerpoint, normalUnitVector, false);
675 }
676 }
677 }
678 }
679 g2.setStroke(helperStrokeRA); // restore default stroke to prevent starnge occasional drawings
680 }
681 }
682
683 private EastNorth getNormalUniVector() {
684 double fac = 1.0 / activeMoveDirection.en.distance(0,0);
685 // mult by factor to get unit vector.
686 EastNorth normalUnitVector = new EastNorth(activeMoveDirection.en.getX() * fac, activeMoveDirection.en.getY() * fac);
687
688 // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
689 // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
690 if (newN1en != null && ((newN1en.getX() > initialN1en.getX()) != (normalUnitVector.getX() > -0.0))) {
691 // If not, use a sign-flipped version of the normalUnitVector.
692 normalUnitVector = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
693 }
694
695 //HACK: swap Y, because the target pixels are top down, but EastNorth is bottom-up.
696 //This is normally done by MapView.getPoint, but it does not work on vectors.
697 normalUnitVector.setLocation(normalUnitVector.getX(), -normalUnitVector.getY());
698 return normalUnitVector;
699 }
700
701 private void drawAngleSymbol(Graphics2D g2, Point2D center, EastNorth normal, boolean mirror) {
702 // EastNorth units per pixel
703 double factor = 1.0/g2.getTransform().getScaleX();
704 double raoffsetx = symbolSize*factor*normal.getX();
705 double raoffsety = symbolSize*factor*normal.getY();
706
707 double cx = center.getX(), cy = center.getY();
708 double k = (mirror ? -1 : 1);
709 Point2D ra1 = new Point2D.Double(cx + raoffsetx, cy + raoffsety);
710 Point2D ra3 = new Point2D.Double(cx - raoffsety*k, cy + raoffsetx*k);
711 Point2D ra2 = new Point2D.Double(ra1.getX() - raoffsety*k, ra1.getY() + raoffsetx*k);
712
713 GeneralPath ra = new GeneralPath();
714 ra.moveTo((float)ra1.getX(), (float)ra1.getY());
715 ra.lineTo((float)ra2.getX(), (float)ra2.getY());
716 ra.lineTo((float)ra3.getX(), (float)ra3.getY());
717 g2.setStroke(helperStrokeRA);
718 g2.draw(ra);
719 }
720
721 /**
722 * Create a new Line that extends off the edge of the viewport in one direction
723 * @param start The start point of the line
724 * @param unitvector A unit vector denoting the direction of the line
725 * @param g the Graphics2D object it will be used on
726 */
727 static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
728 Rectangle bounds = g.getDeviceConfiguration().getBounds();
729 try {
730 AffineTransform invtrans = g.getTransform().createInverse();
731 Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
732 Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
733
734 // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
735 // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
736 // This can be used as a safe length of line to generate which will always go off-viewport.
737 double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
738
739 return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
740 }
741 catch (NoninvertibleTransformException e) {
742 return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
743 }
744 }
745}
Note: See TracBrowser for help on using the repository browser.