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

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

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

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