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

Revision 4982, 26.5 KB checked in by stoecker, 3 months ago (diff)

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

  • Property svn:eol-style set to native
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.