Ticket #3832: josm-2374-extrude-movenormal-ris-v2.diff

File josm-2374-extrude-movenormal-ris-v2.diff, 15.5 KB (added by ris, 14 years ago)
  • src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

     
    1212import java.awt.Point;
    1313import java.awt.event.KeyEvent;
    1414import java.awt.event.MouseEvent;
     15import java.awt.event.ActionEvent;
    1516import java.awt.geom.GeneralPath;
     17import java.awt.geom.Line2D;
     18import java.awt.geom.Line2D.Double;
    1619import java.util.Collection;
    1720import java.util.LinkedList;
    1821
     
    2023import org.openstreetmap.josm.command.AddCommand;
    2124import org.openstreetmap.josm.command.ChangeCommand;
    2225import org.openstreetmap.josm.command.Command;
     26import org.openstreetmap.josm.command.MoveCommand;
    2327import org.openstreetmap.josm.command.SequenceCommand;
    2428import org.openstreetmap.josm.data.coor.EastNorth;
    2529import org.openstreetmap.josm.data.osm.Node;
     30import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2631import org.openstreetmap.josm.data.osm.Way;
    2732import org.openstreetmap.josm.data.osm.WaySegment;
    2833import org.openstreetmap.josm.gui.MapFrame;
     
    3540
    3641/**
    3742 * Makes a rectangle from a line, or modifies a rectangle.
    38  *
    39  * This class currently contains some "sleeping" code copied from DrawAction (move and rotate)
    40  * which can eventually be removed, but it may also get activated here and removed in DrawAction.
    4143 */
    4244public class ExtrudeAction extends MapMode implements MapViewPaintable {
    4345
    44     enum Mode { EXTRUDE, rotate, select }
    45     private Mode mode = null;
     46    enum Mode { extrude, translate, select }
     47    private Mode mode = Mode.select;
    4648    private long mouseDownTime = 0;
    4749    private WaySegment selectedSegment = null;
    4850    private Color selectedColor;
     
    5658     */
    5759    private Cursor oldCursor;
    5860    /**
    59      * The current position of the mouse
    60      */
    61     private Point mousePos;
    62     /**
    6361     * The position of the mouse cursor when the drag action was initiated.
    6462     */
    6563    private Point initialMousePos;
     
    6765     * The time which needs to pass between click and release before something
    6866     * counts as a move, in milliseconds
    6967     */
    70     private int initialMoveDelay = 200;
     68    private static int initialMoveDelay = 200;
     69    /**
     70     * For translation, a the initial EastNorths of node1 and node2
     71     */
     72    private EastNorth initialN1en;
     73    private EastNorth initialN2en;
     74    /**
     75     * This is to work around some deficiencies in MoveCommand when translating
     76     */
     77    private double alreadyTranslatedX;
     78    private double alreadyTranslatedY;
    7179
    7280    /**
    7381     * Create a new SelectAction
     
    116124        Main.map.mapView.removeMouseListener(this);
    117125        Main.map.mapView.removeMouseMotionListener(this);
    118126        Main.map.mapView.removeTemporaryLayer(this);
    119 
    120127    }
    121128
    122129    /**
    123      * If the left mouse button is pressed, move all currently selected
    124      * objects (if one of them is under the mouse) or the current one under the
    125      * mouse (which will become selected).
     130     * Perform action depending on what mode we're in.
    126131     */
    127132    @Override public void mouseDragged(MouseEvent e) {
    128133        if(!Main.map.mapView.isActiveLayerVisible())
    129134            return;
    130         if (mode == Mode.select) return;
    131135
    132         // do not count anything as a move if it lasts less than 100 milliseconds.
    133         if ((mode == Mode.EXTRUDE) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
     136        // do not count anything as a drag if it lasts less than 100 milliseconds.
     137        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return;
    134138
    135         if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
    136             return;
     139        if (mode == Mode.select) {
     140            // Just sit tight and wait for mouse to be released.
     141        } else {
     142            EastNorth en1 = initialN1en;
     143            EastNorth en2 = initialN2en;
     144            // This may be ugly, but I can't see any other way of getting a mapview from here.
     145            EastNorth en3 = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
    137146
    138         if (mode == Mode.EXTRUDE) {
    139             setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    140         }
    141 
    142         if (mousePos == null) {
    143             mousePos = e.getPoint();
    144             return;
    145         }
    146 
    147         Main.map.mapView.repaint();
    148         mousePos = e.getPoint();
    149 
    150     }
    151 
    152     public void paint(Graphics g, MapView mv) {
    153         if (selectedSegment != null) {
    154             Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    155             Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1);
    156 
    157             EastNorth en1 = n1.getEastNorth();
    158             EastNorth en2 = n2.getEastNorth();
    159             EastNorth en3 = mv.getEastNorth(mousePos.x, mousePos.y);
    160 
     147            // calculate base - the point on the segment from which the distance to mouse pos is shortest
    161148            double u = ((en3.east() - en1.east()) * (en2.east() - en1.east()) +
    162149                    (en3.north() - en1.north()) * (en2.north() - en1.north())) /
    163150                    en2.distanceSq(en1);
    164             // the point on the segment from which the distance to mouse pos is shortest
    165151            EastNorth base = new EastNorth(en1.east() + u * (en2.east() - en1.east()),
    166152                    en1.north() + u * (en2.north() - en1.north()));
    167153
     
    174160            xoff = en3.east() - base.east();
    175161            yoff = en3.north() - base.north();
    176162
     163            Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
     164            Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
     165
     166            if (mode == Mode.extrude) {
     167                // This doesn't seem to work so should it be here?
     168                setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
     169            } else if (mode == Mode.translate) {
     170                Command c = !Main.main.undoRedo.commands.isEmpty()
     171                ? Main.main.undoRedo.commands.getLast() : null;
     172                if (c instanceof SequenceCommand) {
     173                    c = ((SequenceCommand)c).getLastCommand();
     174                }
     175
     176                // Better way of testing list equality non-order-sensitively?
     177                if (c instanceof MoveCommand
     178                && ((MoveCommand)c).getMovedNodes().contains(n1)
     179                && ((MoveCommand)c).getMovedNodes().contains(n2)
     180                && ((MoveCommand)c).getMovedNodes().size() == 2) {
     181                    // MoveCommand doesn't let us know how much it has already moved the selection
     182                    // so we have to do some ugly record-keeping.
     183                    double dx = xoff - alreadyTranslatedX;
     184                    double dy = yoff - alreadyTranslatedY;
     185                    ((MoveCommand)c).moveAgain(dx,dy);
     186                    alreadyTranslatedX += dx;
     187                    alreadyTranslatedY += dy;
     188                } else {
     189                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
     190                    nodelist.add(n1);
     191                    nodelist.add(n2);
     192                    Main.main.undoRedo.add(c = new MoveCommand(nodelist, xoff, yoff));
     193                    alreadyTranslatedX += xoff;
     194                    alreadyTranslatedY += yoff;
     195                }
     196            }
     197            Main.map.mapView.repaint();
     198        }
     199    }
     200
     201    public void paint(Graphics g, MapView mv) {
     202        if (mode == Mode.select) {
     203            // Nothing to do
     204        } else {
    177205            Graphics2D g2 = (Graphics2D)g;
    178206            g2.setColor(selectedColor);
    179207            g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    180208            GeneralPath b = new GeneralPath();
    181             Point p1 = mv.getPoint(en1);
    182             Point p2 = mv.getPoint(en2);
    183             Point p3 = mv.getPoint(en1.add(xoff, yoff));
    184             Point p4 = mv.getPoint(en2.add(xoff, yoff));
     209            Point p1 = mv.getPoint(initialN1en);
     210            Point p2 = mv.getPoint(initialN2en);
     211            Point p3 = mv.getPoint(initialN1en.add(xoff, yoff));
     212            Point p4 = mv.getPoint(initialN2en.add(xoff, yoff));
    185213
    186             b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y);
    187             b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
    188             b.lineTo(p1.x, p1.y);
    189             g2.draw(b);
     214            if (mode == Mode.extrude) {
     215                b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y);
     216                b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
     217                b.lineTo(p1.x, p1.y);
     218                g2.draw(b);
     219                g2.setStroke(new BasicStroke(1));
     220            } else if (mode == Mode.translate) {
     221                Line2D newline = new Line2D.Double(p3, p4);
     222                g2.draw(newline);
     223                g2.setStroke(new BasicStroke(2));
     224                Line2D oldline = new Line2D.Double(p1, p2);
     225                g2.draw(oldline);
     226            }
     227
    190228            g2.setStroke(new BasicStroke(1));
    191229        }
    192230    }
    193231
    194232    /**
     233     * If the left mouse button is pressed over a segment, switch
     234     * to either extrude or translate mode depending on whether ctrl is held.
    195235     */
    196236    @Override public void mousePressed(MouseEvent e) {
    197237        if(!Main.map.mapView.isActiveLayerVisible())
     
    203243        // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    204244        // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    205245
    206         mouseDownTime = System.currentTimeMillis();
     246        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint());
    207247
    208         selectedSegment =
    209             Main.map.mapView.getNearestWaySegment(e.getPoint());
     248        if (selectedSegment == null) {
     249            // If nothing gets caught, stay in select mode
     250        } else {
     251            // Otherwise switch to another mode
     252            if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) {
     253                mode = Mode.translate;
     254                alreadyTranslatedX = 0.0;
     255                alreadyTranslatedY = 0.0;
     256            } else {
     257                mode = Mode.extrude;
     258                getCurrentDataSet().setSelected(selectedSegment.way);
     259            }
    210260
    211         mode = (selectedSegment == null) ? Mode.select : Mode.EXTRUDE;
    212         oldCursor = Main.map.mapView.getCursor();
     261            // For extrusion, these positions are actually never changed,
     262            // but keeping note of this anyway allows us to not continually
     263            // look it up and also allows us to unify code with the translate mode
     264            initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth();
     265            initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth();
    213266
    214         updateStatusLine();
    215         Main.map.mapView.addTemporaryLayer(this);
    216         Main.map.mapView.repaint();
     267            oldCursor = Main.map.mapView.getCursor();
     268            Main.map.mapView.addTemporaryLayer(this);
    217269
    218         mousePos = e.getPoint();
    219         initialMousePos = e.getPoint();
     270            updateStatusLine();
     271            Main.map.mapView.repaint();
    220272
    221         if(selectedSegment != null) {
    222             getCurrentDataSet().setSelected(selectedSegment.way);
     273            // Make note of time pressed
     274            mouseDownTime = System.currentTimeMillis();
     275
     276            // Make note of mouse position
     277            initialMousePos = e.getPoint();
     278
     279            xoff = 0;
     280            yoff = 0;
    223281        }
    224282    }
    225283
     
    227285     * Restore the old mouse cursor.
    228286     */
    229287    @Override public void mouseReleased(MouseEvent e) {
     288
    230289        if(!Main.map.mapView.isActiveLayerVisible())
    231290            return;
    232         restoreCursor();
    233         if (selectedSegment == null) return;
    234         if (mousePos.distance(initialMousePos) > 10) {
    235             Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    236             Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
    237             EastNorth en3 = n2.getEastNorth().add(xoff, yoff);
    238             Node n3 = new Node(Main.proj.eastNorth2latlon(en3));
    239             EastNorth en4 = n1.getEastNorth().add(xoff, yoff);
    240             Node n4 = new Node(Main.proj.eastNorth2latlon(en4));
    241             Way wnew = new Way(selectedSegment.way);
    242             wnew.addNode(selectedSegment.lowerIndex+1, n3);
    243             wnew.addNode(selectedSegment.lowerIndex+1, n4);
    244             if (wnew.getNodesCount() == 4) {
    245                 wnew.addNode(n1);
     291
     292        if (mode == mode.select) {
     293            // Nothing to be done
     294        } else {
     295            if (mode == mode.extrude) {
     296                if (e.getPoint().distance(initialMousePos) > 10) {
     297                    // Commit extrusion
     298
     299                    Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
     300                    Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
     301                    EastNorth en3 = n2.getEastNorth().add(xoff, yoff);
     302                    Node n3 = new Node(Main.proj.eastNorth2latlon(en3));
     303                    EastNorth en4 = n1.getEastNorth().add(xoff, yoff);
     304                    Node n4 = new Node(Main.proj.eastNorth2latlon(en4));
     305                    Way wnew = new Way(selectedSegment.way);
     306                    wnew.addNode(selectedSegment.lowerIndex+1, n3);
     307                    wnew.addNode(selectedSegment.lowerIndex+1, n4);
     308                    if (wnew.getNodesCount() == 4) {
     309                        wnew.addNode(n1);
     310                    }
     311                    Collection<Command> cmds = new LinkedList<Command>();
     312                    cmds.add(new AddCommand(n4));
     313                    cmds.add(new AddCommand(n3));
     314                    cmds.add(new ChangeCommand(selectedSegment.way, wnew));
     315                    Command c = new SequenceCommand(tr("Extrude Way"), cmds);
     316                    Main.main.undoRedo.add(c);
     317                }
     318            } else if (mode == mode.translate) {
     319                // I don't think there's anything to do
    246320            }
    247             Collection<Command> cmds = new LinkedList<Command>();
    248             cmds.add(new AddCommand(n4));
    249             cmds.add(new AddCommand(n3));
    250             cmds.add(new ChangeCommand(selectedSegment.way, wnew));
    251             Command c = new SequenceCommand(tr("Extrude Way"), cmds);
    252             Main.main.undoRedo.add(c);
     321
     322            // Switch back into select mode
     323            restoreCursor();
     324            Main.map.mapView.removeTemporaryLayer(this);
     325            selectedSegment = null;
     326            mode = Mode.select;
     327
     328            updateStatusLine();
     329            Main.map.mapView.repaint();
    253330        }
    254 
    255         Main.map.mapView.removeTemporaryLayer(this);
    256         selectedSegment = null;
    257         mode = null;
    258         updateStatusLine();
    259         Main.map.mapView.repaint();
    260331    }
    261332
    262333    @Override public String getModeHelpText() {
    263         if (mode == Mode.select)
    264             return tr("Release the mouse button to select the objects in the rectangle.");
    265         else if (mode == Mode.EXTRUDE)
     334        if (mode == Mode.translate)
     335            return tr("Move a segment along its normal.");
     336        else if (mode == Mode.extrude)
    266337            return tr("Draw a rectangle of the desired size, then release the mouse button.");
    267         else if (mode == Mode.rotate)
    268             return tr("Release the mouse button to stop rotating.");
    269338        else
    270             return tr("Drag a way segment to make a rectangle.");
     339            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
    271340    }
    272341
    273342    @Override public boolean layerIsSupported(Layer l) {