Ticket #5427: extrude-patch-2.diff

File extrude-patch-2.diff, 34.6 KB (added by extropy, 20 months 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() {