Ticket #5427: extrude-patch-2.diff

File extrude-patch-2.diff, 34.6 KB (added by extropy, 11 years ago)

Improved patch, cal extrude in additional directions when moving, always adds nodes if SHIFT is pressed

  • src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

     
    1717import java.awt.geom.Line2D;
    1818import java.awt.geom.NoninvertibleTransformException;
    1919import java.awt.geom.Point2D;
     20import java.util.ArrayList;
    2021import java.util.Collection;
    2122import java.util.LinkedList;
     23import java.util.List;
    2224
    2325import org.openstreetmap.josm.Main;
    2426import org.openstreetmap.josm.command.AddCommand;
     
    4749public class ExtrudeAction extends MapMode implements MapViewPaintable {
    4850
    4951    enum Mode { extrude, translate, select }
     52
    5053    private Mode mode = Mode.select;
     54
     55    /**
     56     * If true, when extruding create new node even if segments prallel.
     57     */
     58    private boolean alwaysCreateNodes = false;
    5159    private long mouseDownTime = 0;
    5260    private WaySegment selectedSegment = null;
    5361    private Color selectedColor;
    5462
    5563    /**
     64     * Possible directions to move to.
     65     */
     66    private List<EastNorth> possibleMoveDirections;
     67
     68    /**
     69     * The direction that is currently active.
     70     */
     71    private EastNorth activeMoveDirection;
     72
     73    /**
    5674     * The old cursor before the user pressed the mouse button.
    5775     */
    5876    private Cursor oldCursor;
     
    7593     */
    7694    private EastNorth newN1en;
    7795    private EastNorth newN2en;
     96
    7897    /**
    79      * This is to work around some deficiencies in MoveCommand when translating
     98     * the command that performed last move.
    8099     */
    81     private EastNorth lastTranslatedN1en;
     100    private MoveCommand moveCommand;
     101
    82102    /**
    83103     * Create a new SelectAction
    84104     * @param mapFrame The MapFrame this action belongs to.
     
    93113        selectedColor = PaintColors.SELECTED.get();
    94114    }
    95115
    96     private static Cursor getCursor(String name, String mod, int def) {
    97         try {
    98             return ImageProvider.getCursor(name, mod);
    99         } catch (Exception e) {
    100         }
    101         return Cursor.getPredefinedCursor(def);
    102     }
    103116
    104     private void setCursor(Cursor c) {
    105         if (oldCursor == null) {
    106             oldCursor = Main.map.mapView.getCursor();
    107             Main.map.mapView.setCursor(c);
    108         }
     117
     118    @Override public String getModeHelpText() {
     119        if (mode == Mode.translate)
     120            return tr("Move a segment along its normal, then release the mouse button.");
     121        else if (mode == Mode.extrude)
     122            return tr("Draw a rectangle of the desired size, then release the mouse button.");
     123        else
     124            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
    109125    }
    110126
    111     private void restoreCursor() {
    112         if (oldCursor != null) {
    113             Main.map.mapView.setCursor(oldCursor);
    114             oldCursor = null;
    115         }
     127    @Override public boolean layerIsSupported(Layer l) {
     128        return l instanceof OsmDataLayer;
    116129    }
    117130
     131
    118132    @Override public void enterMode() {
    119133        super.enterMode();
    120134        Main.map.mapView.addMouseListener(this);
     
    122136    }
    123137
    124138    @Override public void exitMode() {
    125         super.exitMode();
    126139        Main.map.mapView.removeMouseListener(this);
    127140        Main.map.mapView.removeMouseMotionListener(this);
    128141        Main.map.mapView.removeTemporaryLayer(this);
     142        super.exitMode();
    129143    }
    130144
     145
    131146    /**
     147     * If the left mouse button is pressed over a segment, switch
     148     * to either extrude or translate mode depending on whether ctrl is held.
     149     */
     150    @Override public void mousePressed(MouseEvent e) {
     151        if(!Main.map.mapView.isActiveLayerVisible())
     152            return;
     153        if (!(Boolean)this.getValue("active"))
     154            return;
     155        if (e.getButton() != MouseEvent.BUTTON1)
     156            return;
     157
     158        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
     159
     160        if (selectedSegment == null) {
     161            // If nothing gets caught, stay in select mode
     162        } else {
     163            // Otherwise switch to another mode
     164
     165            if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) {
     166                mode = Mode.translate;
     167            } else {
     168                mode = Mode.extrude;
     169                getCurrentDataSet().setSelected(selectedSegment.way);
     170                alwaysCreateNodes = ( (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
     171            }
     172
     173
     174            // remember initial positions for segment nodes.
     175            initialN1en = selectedSegment.getFirstNode().getEastNorth();
     176            initialN2en = selectedSegment.getSecondNode().getEastNorth();
     177
     178            //gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments
     179            possibleMoveDirections = new ArrayList<EastNorth>();
     180            possibleMoveDirections.add(new EastNorth(
     181                    initialN1en.getY() - initialN2en.getY(),
     182                    initialN2en.getX() - initialN1en.getX()));
     183
     184            //add directions parallel to neighbor segments
     185
     186            Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     187            if (prevNode != null)
     188            {
     189                EastNorth en = prevNode.getEastNorth();
     190                possibleMoveDirections.add(new EastNorth(
     191                        initialN1en.getX() - en.getX(),
     192                        initialN1en.getY() - en.getY()));
     193            }
     194
     195            Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     196            if (nextNode != null)
     197            {
     198                EastNorth en = nextNode.getEastNorth();
     199                possibleMoveDirections.add(new EastNorth(
     200                        initialN2en.getX() - en.getX(),
     201                        initialN2en.getY() - en.getY()));
     202            }
     203
     204            // Signifies that nothing has happened yet
     205            newN1en = null;
     206            newN2en = null;
     207            moveCommand = null;
     208
     209            Main.map.mapView.addTemporaryLayer(this);
     210
     211            updateStatusLine();
     212            Main.map.mapView.repaint();
     213
     214            // Make note of time pressed
     215            mouseDownTime = System.currentTimeMillis();
     216
     217            // Make note of mouse position
     218            initialMousePos = e.getPoint();
     219        }
     220    }
     221
     222
     223    /**
    132224     * Perform action depending on what mode we're in.
    133225     */
    134226    @Override public void mouseDragged(MouseEvent e) {
     
    136228            return;
    137229
    138230        // do not count anything as a drag if it lasts less than 100 milliseconds.
    139         if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return;
     231        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)
     232            return;
    140233
    141234        if (mode == Mode.select) {
    142235            // Just sit tight and wait for mouse to be released.
    143236        } else {
    144             Node nd1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    145             Node nd2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1);
     237            //move and extrude mode - move the selected segment
    146238
    147             EastNorth en1 = nd1.getEastNorth();
    148             EastNorth en2 = nd2.getEastNorth();
    149             EastNorth en3 = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
     239            EastNorth initialMouseEn = Main.map.mapView.getEastNorth(initialMousePos.x, initialMousePos.y);
     240            EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
     241            EastNorth mouseMovement = new EastNorth(mouseEn.getX() - initialMouseEn.getX(), mouseEn.getY() - initialMouseEn.getY());
    150242
    151             double u = ((en3.east() - en1.east()) * (en2.east() - en1.east()) +
    152                     (en3.north() - en1.north()) * (en2.north() - en1.north())) /
    153                     en2.distanceSq(en1);
    154             // the point on the segment from which the distance to mouse pos is shortest
    155             EastNorth base = new EastNorth(en1.east() + u * (en2.east() - en1.east()),
    156                     en1.north() + u * (en2.north() - en1.north()));
     243            double bestDistance = Double.POSITIVE_INFINITY;
     244            EastNorth bestMovement = null;
     245            activeMoveDirection = null;
    157246
    158             // find out the distance, in metres, between the base point and the mouse cursor
    159             double distance = Main.proj.eastNorth2latlon(base).greatCircleDistance(Main.proj.eastNorth2latlon(en3));
    160             Main.map.statusLine.setDist(distance);
    161             updateStatusLine();
     247            //find the best movement direction and vector
     248            for(EastNorth direction: possibleMoveDirections){
     249                EastNorth movement = calculateSegmentOffset(initialN1en, initialN2en, direction , mouseEn);
     250                if (movement == null) {
     251                    //if direction parallel to segment.
     252                    continue;
     253                }
    162254
    163             // compute vertical and horizontal components.
    164             double xoff = en3.east() - base.east();
    165             double yoff = en3.north() - base.north();
     255                double distanceFromMouseMovement = movement.distance(mouseMovement);
     256                if (bestDistance > distanceFromMouseMovement){
     257                    bestDistance = distanceFromMouseMovement;
     258                    activeMoveDirection = direction;
     259                    bestMovement = movement;
     260                }
     261            }
    166262
    167             newN1en = new EastNorth(en1.getX() + xoff, en1.getY() + yoff);
    168             newN2en = new EastNorth(en2.getX() + xoff, en2.getY() + yoff);
     263            newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
     264            newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
    169265
    170             // find out the distance, in metres, between the initial position of N1 and the new one.
    171             Main.map.statusLine.setDist(Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en)));
     266            // find out the movement distance, in metres
     267            double distance = Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en));
     268            Main.map.statusLine.setDist(distance);
    172269            updateStatusLine();
    173270
    174271            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    175272
    176273            if (mode == Mode.extrude) {
    177 
     274                //nothing here
    178275            } else if (mode == Mode.translate) {
    179                 Command c = !Main.main.undoRedo.commands.isEmpty()
    180                 ? Main.main.undoRedo.commands.getLast() : null;
    181                 if (c instanceof SequenceCommand) {
    182                     c = ((SequenceCommand)c).getLastCommand();
     276                //move nodes to new position
     277                if (moveCommand == null)
     278                {
     279                    //make a new move command
     280                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
     281                    nodelist.add(selectedSegment.getFirstNode());
     282                    nodelist.add(selectedSegment.getSecondNode());
     283                    moveCommand = new MoveCommand(nodelist, bestMovement.getX(), bestMovement.getY());
     284                    Main.main.undoRedo.add(moveCommand);
     285                } else {
     286                    //reuse existing move command
     287                    moveCommand.undoCommand();
     288                    moveCommand.moveAgain(bestMovement.getX(), bestMovement.getY());
    183289                }
     290            }
    184291
    185                 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    186                 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
     292            Main.map.mapView.repaint();
     293        }
     294    }
    187295
    188                 EastNorth difference = new EastNorth(newN1en.getX()-lastTranslatedN1en.getX(), newN1en.getY()-lastTranslatedN1en.getY());
    189296
    190                 // Better way of testing list equality non-order-sensitively?
    191                 if (c instanceof MoveCommand
    192                         && ((MoveCommand)c).getParticipatingPrimitives().contains(n1)
    193                         && ((MoveCommand)c).getParticipatingPrimitives().contains(n2)
    194                         && ((MoveCommand)c).getParticipatingPrimitives().size() == 2) {
    195                     // MoveCommand doesn't let us know how much it has already moved the selection
    196                     // so we have to do some ugly record-keeping.
    197                     ((MoveCommand)c).moveAgain(difference.getX(), difference.getY());
    198                     lastTranslatedN1en = newN1en;
    199                 } else {
    200                     Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
    201                     nodelist.add(n1);
    202                     nodelist.add(n2);
    203                     Main.main.undoRedo.add(c = new MoveCommand(nodelist, difference.getX(), difference.getY()));
    204                     lastTranslatedN1en = newN1en;
     297    /**
     298     * Do anything that needs to be done, then switch back to select mode
     299     */
     300    @Override public void mouseReleased(MouseEvent e) {
     301
     302        if(!Main.map.mapView.isActiveLayerVisible())
     303            return;
     304
     305        if (mode == Mode.select) {
     306            // Nothing to be done
     307        } else {
     308            if (mode == Mode.extrude) {
     309                if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
     310                    // create extrusion
     311
     312                    Collection<Command> cmds = new LinkedList<Command>();
     313                    Way wnew = new Way(selectedSegment.way);
     314                    int insertionPoint = selectedSegment.lowerIndex + 1;
     315
     316                    //find if the new points overlap existing segments (in case of 90 degree angles)
     317                    Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     318                    boolean nodeOverlapsSegment = prevNode != null && pointsColinear(prevNode.getEastNorth(), initialN1en, newN1en);
     319
     320                    if (nodeOverlapsSegment && !alwaysCreateNodes){
     321                        //move existing node
     322                        Node n1Old = selectedSegment.getFirstNode();
     323                        cmds.add(new MoveCommand(n1Old, Main.proj.eastNorth2latlon(newN1en)));
     324                    } else{
     325                        //introduce new node
     326                        Node n1New = new Node(Main.proj.eastNorth2latlon(newN1en));
     327                        wnew.addNode(insertionPoint, n1New);
     328                        insertionPoint ++;
     329                        cmds.add(new AddCommand(n1New));
     330                    }
     331
     332                    //find if the new points overlap existing segments (in case of 90 degree angles)
     333                    Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     334                    nodeOverlapsSegment = nextNode != null && pointsColinear(nextNode.getEastNorth(), initialN2en, newN2en);
     335
     336                    if (nodeOverlapsSegment && !alwaysCreateNodes){
     337                        //move existing node
     338                        Node n2Old = selectedSegment.getSecondNode();
     339                        cmds.add(new MoveCommand(n2Old, Main.proj.eastNorth2latlon(newN2en)));
     340                    } else{
     341                        //introduce new node
     342                        Node n2New = new Node(Main.proj.eastNorth2latlon(newN2en));
     343                        wnew.addNode(insertionPoint, n2New);
     344                        insertionPoint ++;
     345                        cmds.add(new AddCommand(n2New));
     346                    }
     347
     348                    //the way was a single segment, close the way
     349                    if (wnew.getNodesCount() == 4) {
     350                        wnew.addNode(selectedSegment.getFirstNode());
     351                    }
     352
     353                    cmds.add(new ChangeCommand(selectedSegment.way, wnew));
     354                    Command c = new SequenceCommand(tr("Extrude Way"), cmds);
     355                    Main.main.undoRedo.add(c);
    205356                }
     357            } else if (mode == Mode.translate) {
     358                //Commit translate
     359                //the move command is already committed in mouseDragged
    206360            }
     361
     362            // Switch back into select mode
     363            restoreCursor();
     364            Main.map.mapView.removeTemporaryLayer(this);
     365            selectedSegment = null;
     366            moveCommand = null;
     367            mode = Mode.select;
     368
     369            updateStatusLine();
    207370            Main.map.mapView.repaint();
    208371        }
    209372    }
    210373
     374    /***
     375     * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position.
     376     * @param segmentP1
     377     * @param segmentP2
     378     * @param targetPos
     379     * @return offset amount of P1 and P2.
     380     */
     381    private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2 ,EastNorth moveDirection , EastNorth targetPos)
     382    {
     383        EastNorth intersectionPoint = getLineLineIntersection(
     384                segmentP1,
     385                segmentP2,
     386                targetPos,
     387                new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY()));
     388
     389        if (intersectionPoint == null)
     390            return null;
     391        else
     392            //return distance form base to target position
     393            return new EastNorth(targetPos.getX() - intersectionPoint.getX(), targetPos.getY() - intersectionPoint.getY());
     394    }
     395
    211396    /**
    212      * Create a new Line that extends off the edge of the viewport in one direction
    213      * @param start The start point of the line
    214      * @param unitvector A unit vector denoting the direction of the line
    215      * @param g the Graphics2D object  it will be used on
     397     * Finds the intersection of two lines of inifinite length
     398     * @return EastNorth null if no intersection was found, the Lon coordinates of the intersection otherwise
    216399     */
    217     static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
    218         Rectangle bounds = g.getDeviceConfiguration().getBounds();
    219         try {
    220             AffineTransform invtrans = g.getTransform().createInverse();
    221             Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
    222             Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
     400    static public EastNorth getLineLineIntersection(
     401            EastNorth p1, EastNorth p2,
     402            EastNorth p3, EastNorth p4) {
    223403
    224             // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
    225             // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
    226             // This can be used as a safe length of line to generate which will always go off-viewport.
    227             double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
     404        // Convert line from (point, point) form to ax+by=c
     405        double a1 = p2.getY() - p1.getY();
     406        double b1 = p1.getX() - p2.getX();
     407        double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();
    228408
    229             return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
     409        double a2 = p4.getY() - p3.getY();
     410        double b2 = p3.getX() - p4.getX();
     411        double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();
     412
     413        // Solve the equations
     414        double det = a1*b2 - a2*b1;
     415        if(det == 0) return null; // Lines are parallel
     416
     417        return new EastNorth(
     418                (b1*c2 - b2*c1)/det,
     419                (a2*c1 - a1*c2)/det);
     420    }
     421
     422
     423    /**
     424     * Returns true if all points are on the same line.
     425     */
     426    private static boolean pointsColinear(EastNorth p1, EastNorth p2, EastNorth p3){
     427
     428        //the simple dumb way of triangle side lengths.
     429        double distance1 = p1.distance(p2);
     430        double distance2 = p1.distance(p3);
     431        double distance3 = p2.distance(p3);
     432
     433        //sort so that distance 1 is the greatest
     434        if (distance1 < distance2){
     435            double temp = distance1;
     436            distance1 = distance2;
     437            distance2 = temp;
    230438        }
    231         catch (NoninvertibleTransformException e) {
    232             return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
     439
     440        if (distance1 < distance3){
     441            double temp = distance1;
     442            distance1 = distance3;
     443            distance3 = temp;
    233444        }
     445
     446        //test with some treshold
     447        double difference = distance1 - distance2 - distance3;
     448
     449        return (Math.abs(difference) < 1e-15);
     450
    234451    }
    235452
     453    /**
     454     * Gets a node from selected way before given index.
     455     * @param index  index of current node
     456     * @return previous node or null if there are no nodes there.
     457     */
     458    private Node getPreviousNode(int index){
     459        if (index > 0)
     460            return selectedSegment.way.getNode(index - 1);
     461        else if (selectedSegment.way.isClosed())
     462            return selectedSegment.way.getNode(selectedSegment.way.getNodesCount() - 2);
     463        else
     464            return null;
     465    }
     466
     467    /**
     468     * Gets a node from selected way before given index.
     469     * @param index index of current node
     470     * @return next node or null if there are no nodes there.
     471     */
     472    private Node getNextNode(int index){
     473        int count = selectedSegment.way.getNodesCount();
     474        if (index <  count - 1)
     475            return selectedSegment.way.getNode(index + 1);
     476        else if (selectedSegment.way.isClosed())
     477            return selectedSegment.way.getNode(1);
     478        else
     479            return null;
     480    }
     481
    236482    public void paint(Graphics2D g, MapView mv, Bounds box) {
    237483        if (mode == Mode.select) {
    238484            // Nothing to do
     
    263509                    Line2D oldline = new Line2D.Double(p1, p2);
    264510                    g2.draw(oldline);
    265511
    266                     EastNorth segmentVector = new EastNorth(initialN2en.getX()-initialN1en.getX(), initialN2en.getY()-initialN1en.getY());
     512                    if (activeMoveDirection != null) {
    267513
    268                     double fac = 1.0 / Math.hypot(segmentVector.getX(), segmentVector.getY());
    269                     // swap coords to get normal, mult by factor to get unit vector.
    270                     EastNorth normalUnitVector = new EastNorth(segmentVector.getY() * fac, segmentVector.getX() * fac);
     514                        double fac = 1.0 / activeMoveDirection.distance(0,0);
     515                        // mult by factor to get unit vector.
     516                        EastNorth normalUnitVector = new EastNorth(activeMoveDirection.getX() * fac, activeMoveDirection.getY() * fac);
    271517
    272                     // Draw a guideline along the normal.
    273                     Line2D normline;
    274                     Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
    275                     EastNorth drawnorm;
    276                     // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
    277                     // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
    278                     if (newN1en == null || (newN1en.getX() > initialN1en.getX() == normalUnitVector.getX() > -0.0)) {
    279                         drawnorm = normalUnitVector;
    280                     } else {
    281                         // If not, use a sign-flipped version of the normalUnitVector.
    282                         drawnorm = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
    283                     }
    284                     normline = createSemiInfiniteLine(centerpoint, drawnorm, g2);
    285                     g2.draw(normline);
     518                        // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
     519                        // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
     520                        if (newN1en != null && (newN1en.getX() > initialN1en.getX() != normalUnitVector.getX() > -0.0)) {
     521                            // If not, use a sign-flipped version of the normalUnitVector.
     522                            normalUnitVector = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
     523                        }
    286524
    287                     // EastNorth units per pixel
    288                     double factor = 1.0/g2.getTransform().getScaleX();
     525                        //HACK: swap Y, because the target pixels are top down, but EastNorth is bottom-up.
     526                        //This is normally done by MapView.getPoint, but it does not work on vectors.
     527                        normalUnitVector.setLocation(normalUnitVector.getX(), -normalUnitVector.getY());
    289528
    290                     // Draw right angle marker on initial position.
    291                     double raoffsetx = 8.0*factor*drawnorm.getX();
    292                     double raoffsety = 8.0*factor*drawnorm.getY();
    293                     Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
    294                     Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
    295                     Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
    296                     GeneralPath ra = new GeneralPath();
    297                     ra.moveTo((float)ra1.getX(), (float)ra1.getY());
    298                     ra.lineTo((float)ra2.getX(), (float)ra2.getY());
    299                     ra.lineTo((float)ra3.getX(), (float)ra3.getY());
    300                     g2.draw(ra);
     529                        // Draw a guideline along the normal.
     530                        Line2D normline;
     531                        Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
     532                        normline = createSemiInfiniteLine(centerpoint, normalUnitVector, g2);
     533                        g2.draw(normline);
     534
     535                        // Draw right angle marker on initial position, only when moving at right angle
     536                        if (activeMoveDirection == possibleMoveDirections.get(0)) {
     537                            // EastNorth units per pixel
     538                            double factor = 1.0/g2.getTransform().getScaleX();
     539
     540                            double raoffsetx = 8.0*factor*normalUnitVector.getX();
     541                            double raoffsety = 8.0*factor*normalUnitVector.getY();
     542                            Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
     543                            Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
     544                            Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
     545                            GeneralPath ra = new GeneralPath();
     546                            ra.moveTo((float)ra1.getX(), (float)ra1.getY());
     547                            ra.lineTo((float)ra2.getX(), (float)ra2.getY());
     548                            ra.lineTo((float)ra3.getX(), (float)ra3.getY());
     549                            g2.draw(ra);
     550                        }
     551                    }
    301552                }
    302553            }
    303554        }
    304555    }
    305556
     557
     558
    306559    /**
    307      * If the left mouse button is pressed over a segment, switch
    308      * to either extrude or translate mode depending on whether ctrl is held.
     560     * Create a new Line that extends off the edge of the viewport in one direction
     561     * @param start The start point of the line
     562     * @param unitvector A unit vector denoting the direction of the line
     563     * @param g the Graphics2D object  it will be used on
    309564     */
    310     @Override public void mousePressed(MouseEvent e) {
    311         if(!Main.map.mapView.isActiveLayerVisible())
    312             return;
    313         if (!(Boolean)this.getValue("active")) return;
    314         if (e.getButton() != MouseEvent.BUTTON1)
    315             return;
    316         // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    317         // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    318         // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     565    static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
     566        Rectangle bounds = g.getDeviceConfiguration().getBounds();
     567        try {
     568            AffineTransform invtrans = g.getTransform().createInverse();
     569            Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
     570            Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
    319571
    320         selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
     572            // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
     573            // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
     574            // This can be used as a safe length of line to generate which will always go off-viewport.
     575            double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
    321576
    322         if (selectedSegment == null) {
    323             // If nothing gets caught, stay in select mode
    324         } else {
    325             // Otherwise switch to another mode
    326 
    327             // For extrusion, these positions are actually never changed,
    328             // but keeping note of this anyway allows us to not continually
    329             // look it up and also allows us to unify code with the translate mode
    330             initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth();
    331             initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth();
    332 
    333             // Signifies that nothing has happened yet
    334             newN1en = null;
    335             newN2en = null;
    336 
    337             Main.map.mapView.addTemporaryLayer(this);
    338 
    339             updateStatusLine();
    340             Main.map.mapView.repaint();
    341 
    342             // Make note of time pressed
    343             mouseDownTime = System.currentTimeMillis();
    344 
    345             // Make note of mouse position
    346             initialMousePos = e.getPoint();
    347 
    348             // Switch mode.
    349             if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) {
    350                 mode = Mode.translate;
    351                 lastTranslatedN1en = initialN1en;
    352             } else {
    353                 mode = Mode.extrude;
    354                 getCurrentDataSet().setSelected(selectedSegment.way);
    355             }
     577            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
    356578        }
     579        catch (NoninvertibleTransformException e) {
     580            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
     581        }
    357582    }
    358583
    359     /**
    360      * Do anything that needs to be done, then switch back to select mode
    361      */
    362     @Override public void mouseReleased(MouseEvent e) {
    363 
    364         if(!Main.map.mapView.isActiveLayerVisible())
    365             return;
    366 
    367         if (mode == Mode.select) {
    368             // Nothing to be done
    369         } else {
    370             if (mode == Mode.extrude) {
    371                 if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
    372                     // Commit extrusion
    373 
    374                     Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    375                     //Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
    376                     Node n3 = new Node(Main.proj.eastNorth2latlon(newN2en));
    377                     Node n4 = new Node(Main.proj.eastNorth2latlon(newN1en));
    378                     Way wnew = new Way(selectedSegment.way);
    379                     wnew.addNode(selectedSegment.lowerIndex+1, n3);
    380                     wnew.addNode(selectedSegment.lowerIndex+1, n4);
    381                     if (wnew.getNodesCount() == 4) {
    382                         wnew.addNode(n1);
    383                     }
    384                     Collection<Command> cmds = new LinkedList<Command>();
    385                     cmds.add(new AddCommand(n4));
    386                     cmds.add(new AddCommand(n3));
    387                     cmds.add(new ChangeCommand(selectedSegment.way, wnew));
    388                     Command c = new SequenceCommand(tr("Extrude Way"), cmds);
    389                     Main.main.undoRedo.add(c);
    390                 }
    391             } else if (mode == Mode.translate) {
    392                 // I don't think there's anything to do
    393             }
    394 
    395             // Switch back into select mode
    396             restoreCursor();
    397             Main.map.mapView.removeTemporaryLayer(this);
    398             selectedSegment = null;
    399             mode = Mode.select;
    400 
    401             updateStatusLine();
    402             Main.map.mapView.repaint();
     584    private static Cursor getCursor(String name, String mod, int def) {
     585        try {
     586            return ImageProvider.getCursor(name, mod);
     587        } catch (Exception e) {
    403588        }
     589        return Cursor.getPredefinedCursor(def);
    404590    }
    405591
    406     @Override public String getModeHelpText() {
    407         if (mode == Mode.translate)
    408             return tr("Move a segment along its normal, then release the mouse button.");
    409         else if (mode == Mode.extrude)
    410             return tr("Draw a rectangle of the desired size, then release the mouse button.");
    411         else
    412             return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
     592    private void setCursor(Cursor c) {
     593        if (oldCursor == null) {
     594            oldCursor = Main.map.mapView.getCursor();
     595            Main.map.mapView.setCursor(c);
     596        }
    413597    }
    414598
    415     @Override public boolean layerIsSupported(Layer l) {
    416         return l instanceof OsmDataLayer;
     599    private void restoreCursor() {
     600        if (oldCursor != null) {
     601            Main.map.mapView.setCursor(oldCursor);
     602            oldCursor = null;
     603        }
    417604    }
    418605}
  • src/org/openstreetmap/josm/data/osm/WaySegment.java

     
    2121        lowerIndex = i;
    2222    }
    2323
     24    public Node getFirstNode(){
     25        return way.getNode(lowerIndex);
     26    }
     27
     28    public Node getSecondNode(){
     29        return way.getNode(lowerIndex + 1);
     30    }
     31
    2432    @Override public boolean equals(Object o) {
    2533        return o != null && o instanceof WaySegment
    26             && ((WaySegment) o).way == way
    27             && ((WaySegment) o).lowerIndex == lowerIndex;
     34        && ((WaySegment) o).way == way
     35        && ((WaySegment) o).lowerIndex == lowerIndex;
    2836    }
    2937
    3038    @Override public int hashCode() {