Ignore:
Timestamp:
2014-06-05T10:59:30+02:00 (10 years ago)
Author:
akks
Message:

Add Dual Align mode for extruder (patch by AlfonZ, see #7991)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

    r7180 r7216  
    99import java.awt.BasicStroke;
    1010import java.awt.Color;
     11import java.awt.Component;
    1112import java.awt.Cursor;
    1213import java.awt.Graphics2D;
     14import java.awt.KeyboardFocusManager;
    1315import java.awt.Point;
    1416import java.awt.Rectangle;
     
    1719import java.awt.event.AWTEventListener;
    1820import java.awt.event.ActionEvent;
     21import java.awt.event.ActionListener;
    1922import java.awt.event.InputEvent;
    2023import java.awt.event.KeyEvent;
     
    2932import java.util.LinkedList;
    3033import java.util.List;
     34import java.util.Set;
     35import java.util.TreeSet;
     36import javax.swing.JCheckBoxMenuItem;
     37import javax.swing.JFrame;
     38import javax.swing.JMenuItem;
     39import javax.swing.SwingUtilities;
     40import javax.swing.Timer;
    3141
    3242import org.openstreetmap.josm.Main;
     43import org.openstreetmap.josm.actions.JosmAction;
    3344import org.openstreetmap.josm.command.AddCommand;
    3445import org.openstreetmap.josm.command.ChangeCommand;
     
    4354import org.openstreetmap.josm.data.osm.WaySegment;
    4455import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
     56import org.openstreetmap.josm.gui.MainMenu;
    4557import org.openstreetmap.josm.gui.MapFrame;
    4658import org.openstreetmap.josm.gui.MapView;
     
    6375
    6476    /**
    65      * If true, when extruding create new node even if segments parallel.
     77     * If {@code true}, when extruding create new node(s) even if segments are parallel.
    6678     */
    6779    private boolean alwaysCreateNodes = false;
     
    95107     * Collection of nodes that is moved
    96108     */
    97     private Collection<OsmPrimitive> movingNodeList;
     109    private ArrayList<OsmPrimitive> movingNodeList;
    98110
    99111    /**
     
    131143     */
    132144    private MoveCommand moveCommand;
     145    /**
     146     *  The command used for dual alignment movement.
     147     *  Needs to be separate, due to two nodes moving in different directions.
     148     */
     149    private MoveCommand moveCommand2;
    133150
    134151    /** The cursor for the 'create_new' mode. */
     
    153170            this.perpendicular = perpendicular;
    154171        }
    155     }
    156 
    157     /**
    158      * This listener is used to indicate the 'create_new' mode, if the Alt modifier is pressed.
     172
     173        @Override
     174        public String toString() {
     175            return "ReferenceSegment[en=" + en + ", p1=" + p1 + ", p2=" + p2 + ", perp=" + perpendicular + "]";
     176        }
     177    }
     178
     179    // Dual alignment mode stuff
     180    /** {@code true}, if dual alignment mode is enabled. User wants following extrude to be dual aligned. */
     181    private boolean dualAlignEnabled;
     182    /** {@code true}, if dual alignment is active. User is dragging the mouse, required conditions are met. Treat {@link #mode} (extrude/translate/create_new) as dual aligned. */
     183    private boolean dualAlignActive;
     184    /** Dual alignment reference segments */
     185    private ReferenceSegment dualAlignSegment1, dualAlignSegment2;
     186    // Dual alignment UI stuff
     187    private final DualAlignChangeAction dualAlignChangeAction;
     188    private final JCheckBoxMenuItem dualAlignCheckboxMenuItem;
     189    private final Shortcut dualAlignShortcut;
     190    private boolean useRepeatedShortcut;
     191
     192    private class DualAlignChangeAction extends JosmAction {
     193        public DualAlignChangeAction() {
     194            super(tr("Dual alignment"), "mapmode/extrude/dualalign",
     195                    tr("Switch dual alignment mode while extruding"), null, false);
     196            putValue("help", ht("/Action/Extrude#DualAlign"));
     197        }
     198
     199        @Override
     200        public void actionPerformed(ActionEvent e) {
     201            toggleDualAlign();
     202        }
     203    }
     204
     205    /**
     206     * Creates a new ExtrudeAction
     207     * @param mapFrame The MapFrame this action belongs to.
     208     */
     209    public ExtrudeAction(MapFrame mapFrame) {
     210        super(tr("Extrude"), "extrude/extrude", tr("Create areas"),
     211                Shortcut.registerShortcut("mapmode:extrude", tr("Mode: {0}", tr("Extrude")), KeyEvent.VK_X, Shortcut.DIRECT),
     212                mapFrame,
     213                ImageProvider.getCursor("normal", "rectangle"));
     214        putValue("help", ht("/Action/Extrude"));
     215        cursorCreateNew = ImageProvider.getCursor("normal", "rectangle_plus");
     216        cursorTranslate = ImageProvider.getCursor("normal", "rectangle_move");
     217        cursorCreateNodes = ImageProvider.getCursor("normal", "rectangle_plussmall");
     218
     219        dualAlignEnabled = false;
     220        dualAlignChangeAction = new DualAlignChangeAction();
     221        dualAlignCheckboxMenuItem = addDualAlignMenuItem();
     222        dualAlignCheckboxMenuItem.getAction().setEnabled(false);
     223        dualAlignCheckboxMenuItem.setState(dualAlignEnabled);
     224        dualAlignShortcut = Shortcut.registerShortcut("mapmode:extrudedualalign",
     225                tr("Mode: {0}", tr("Extrude Dual alignment")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
     226        useRepeatedShortcut = Main.pref.getBoolean("extrude.dualalign.toggleOnRepeatedX", true);
     227        timer = new Timer(0, new ActionListener() {
     228            @Override
     229            public void actionPerformed(ActionEvent ae) {
     230                timer.stop();
     231                if (set.remove(releaseEvent.getKeyCode())) {
     232                    doKeyReleaseEvent(releaseEvent);
     233                }
     234            }
     235        });
     236    }
     237
     238    @Override
     239    public void destroy() {
     240        super.destroy();
     241        dualAlignChangeAction.destroy();
     242    }
     243
     244    private JCheckBoxMenuItem addDualAlignMenuItem() {
     245        int n = Main.main.menu.editMenu.getItemCount();
     246        for (int i = n-1; i>0; i--) {
     247            JMenuItem item = Main.main.menu.editMenu.getItem(i);
     248            if (item != null && item.getAction() != null && item.getAction() instanceof DualAlignChangeAction) {
     249                Main.main.menu.editMenu.remove(i);
     250            }
     251        }
     252        return MainMenu.addWithCheckbox(Main.main.menu.editMenu, dualAlignChangeAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE);
     253    }
     254
     255    // -------------------------------------------------------------------------
     256    // Mode methods
     257    // -------------------------------------------------------------------------
     258
     259    @Override
     260    public String getModeHelpText() {
     261        StringBuilder rv;
     262        if (mode == Mode.select) {
     263            rv = new StringBuilder(tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " +
     264                "Alt-drag to create a new rectangle, double click to add a new node."));
     265            if (dualAlignEnabled)
     266                rv.append(" ").append(tr("Dual alignment active."));
     267        } else {
     268            if (mode == Mode.translate)
     269                rv = new StringBuilder(tr("Move a segment along its normal, then release the mouse button."));
     270            else if (mode == Mode.translate_node)
     271                rv = new StringBuilder(tr("Move the node along one of the segments, then release the mouse button."));
     272            else if (mode == Mode.extrude)
     273                rv = new StringBuilder(tr("Draw a rectangle of the desired size, then release the mouse button."));
     274            else if (mode == Mode.create_new)
     275                rv = new StringBuilder(tr("Draw a rectangle of the desired size, then release the mouse button."));
     276            else {
     277                Main.warn("Extrude: unknown mode " + mode);
     278                rv = new StringBuilder();
     279            }
     280            if (dualAlignActive)
     281                rv.append(" ").append(tr("Dual alignment active."));
     282        }
     283        return rv.toString();
     284    }
     285
     286    @Override
     287    public boolean layerIsSupported(Layer l) {
     288        return l instanceof OsmDataLayer;
     289    }
     290
     291    @Override
     292    public void enterMode() {
     293        super.enterMode();
     294        Main.map.mapView.addMouseListener(this);
     295        Main.map.mapView.addMouseMotionListener(this);
     296        try {
     297            Toolkit.getDefaultToolkit().addAWTEventListener(altKeyListener, AWTEvent.KEY_EVENT_MASK);
     298        } catch (SecurityException ex) {
     299            Main.warn(ex);
     300        }
     301        initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
     302        initialMoveThreshold = Main.pref.getInteger("extrude.initial-move-threshold", 1);
     303        mainColor = Main.pref.getColor(marktr("Extrude: main line"), null);
     304        if (mainColor == null) mainColor = PaintColors.SELECTED.get();
     305        helperColor = Main.pref.getColor(marktr("Extrude: helper line"), Color.ORANGE);
     306        helperStrokeDash = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.helper-line", "1 4"));
     307        helperStrokeRA = new BasicStroke(1);
     308        symbolSize = Main.pref.getDouble("extrude.angle-symbol-radius", 8);
     309        nodeDragWithoutCtrl = Main.pref.getBoolean("extrude.drag-nodes-without-ctrl", false);
     310        oldLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.ctrl.stroke.old-line", "1"));
     311        mainStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.main", "3"));
     312
     313        ignoreSharedNodes = Main.pref.getBoolean("extrude.ignore-shared-nodes", true);
     314        dualAlignCheckboxMenuItem.getAction().setEnabled(true);
     315    }
     316
     317    @Override
     318    public void exitMode() {
     319        Main.map.mapView.removeMouseListener(this);
     320        Main.map.mapView.removeMouseMotionListener(this);
     321        Main.map.mapView.removeTemporaryLayer(this);
     322        dualAlignCheckboxMenuItem.getAction().setEnabled(false);
     323        try {
     324            Toolkit.getDefaultToolkit().removeAWTEventListener(altKeyListener);
     325        } catch (SecurityException ex) {
     326            Main.warn(ex);
     327        }
     328        super.exitMode();
     329    }
     330
     331    // -------------------------------------------------------------------------
     332    // Event handlers
     333    // -------------------------------------------------------------------------
     334
     335    /**
     336     * This listener is used to indicate different modes via cursor when the Alt/Ctrl/Shift modifier is pressed,
     337     * and for listening to dual alignment shortcuts.
    159338     */
    160339    private final AWTEventListener altKeyListener = new AWTEventListener() {
     
    170349                Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this);
    171350            }
     351            if (e instanceof KeyEvent) {
     352                KeyEvent ke = (KeyEvent) e;
     353                if (dualAlignShortcut.isEvent(ke) || (useRepeatedShortcut && getShortcut().isEvent(ke))) {
     354                    Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
     355                    if (SwingUtilities.getWindowAncestor(focused) instanceof JFrame) {
     356                        processKeyEvent(ke);
     357                    }
     358                }
     359            }
    172360        }
    173361    };
    174362
    175     /**
    176      * Create a new SelectAction
    177      * @param mapFrame The MapFrame this action belongs to.
    178      */
    179     public ExtrudeAction(MapFrame mapFrame) {
    180         super(tr("Extrude"), "extrude/extrude", tr("Create areas"),
    181                 Shortcut.registerShortcut("mapmode:extrude", tr("Mode: {0}", tr("Extrude")), KeyEvent.VK_X, Shortcut.DIRECT),
    182                 mapFrame,
    183                 ImageProvider.getCursor("normal", "rectangle"));
    184         putValue("help", ht("/Action/Extrude"));
    185         cursorCreateNew = ImageProvider.getCursor("normal", "rectangle_plus");
    186         cursorTranslate = ImageProvider.getCursor("normal", "rectangle_move");
    187         cursorCreateNodes = ImageProvider.getCursor("normal", "rectangle_plussmall");
    188     }
    189 
    190     @Override public String getModeHelpText() {
    191         if (mode == Mode.translate)
    192             return tr("Move a segment along its normal, then release the mouse button.");
    193         else if (mode == Mode.extrude)
    194             return tr("Draw a rectangle of the desired size, then release the mouse button.");
    195         else if (mode == Mode.create_new)
    196             return tr("Draw a rectangle of the desired size, then release the mouse button.");
    197         else
    198             return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " +
    199             "Alt-drag to create a new rectangle, double click to add a new node.");
    200     }
    201 
    202     @Override public boolean layerIsSupported(Layer l) {
    203         return l instanceof OsmDataLayer;
    204     }
    205 
    206     @Override public void enterMode() {
    207         super.enterMode();
    208         Main.map.mapView.addMouseListener(this);
    209         Main.map.mapView.addMouseMotionListener(this);
    210         try {
    211             Toolkit.getDefaultToolkit().addAWTEventListener(altKeyListener, AWTEvent.KEY_EVENT_MASK);
    212         } catch (SecurityException ex) {
    213             Main.warn(ex);
    214         }
    215         initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
    216         initialMoveThreshold = Main.pref.getInteger("extrude.initial-move-threshold", 1);
    217         mainColor = Main.pref.getColor(marktr("Extrude: main line"), null);
    218         if (mainColor == null) mainColor = PaintColors.SELECTED.get();
    219         helperColor = Main.pref.getColor(marktr("Extrude: helper line"), Color.ORANGE);
    220         helperStrokeDash = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.helper-line", "1 4"));
    221         helperStrokeRA = new BasicStroke(1);
    222         symbolSize = Main.pref.getDouble("extrude.angle-symbol-radius", 8);
    223         nodeDragWithoutCtrl = Main.pref.getBoolean("extrude.drag-nodes-without-ctrl", false);
    224         oldLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.ctrl.stroke.old-line", "1"));
    225         mainStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.main", "3"));
    226 
    227         ignoreSharedNodes = Main.pref.getBoolean("extrude.ignore-shared-nodes", true);
    228     }
    229 
    230     @Override public void exitMode() {
    231         Main.map.mapView.removeMouseListener(this);
    232         Main.map.mapView.removeMouseMotionListener(this);
    233         Main.map.mapView.removeTemporaryLayer(this);
    234         try {
    235             Toolkit.getDefaultToolkit().removeAWTEventListener(altKeyListener);
    236         } catch (SecurityException ex) {
    237             Main.warn(ex);
    238         }
    239         super.exitMode();
    240     }
    241 
    242     /**
    243      * If the left mouse button is pressed over a segment, switch
    244      * to either extrude, translate or create_new mode depending on whether Ctrl or Alt is held.
    245      */
    246     @Override public void mousePressed(MouseEvent e) {
     363    // events for crossplatform key holding processing
     364    // thanks to http://www.arco.in-berlin.de/keyevent.html
     365    private final Set<Integer> set = new TreeSet<Integer>();
     366    private KeyEvent releaseEvent;
     367    private Timer timer;
     368    private void processKeyEvent(KeyEvent e) {
     369        if (!dualAlignShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e)))
     370            return;
     371
     372        if (e.getID() == KeyEvent.KEY_PRESSED) {
     373            if (timer.isRunning()) {
     374                timer.stop();
     375            } else if (set.add((e.getKeyCode()))) {
     376                doKeyPressEvent(e);
     377            }
     378        } else if (e.getID() == KeyEvent.KEY_RELEASED) {
     379            if (timer.isRunning()) {
     380                timer.stop();
     381                if (set.remove(e.getKeyCode())) {
     382                    doKeyReleaseEvent(e);
     383                }
     384            } else {
     385                releaseEvent = e;
     386                timer.restart();
     387            }
     388        }
     389    }
     390
     391    private void doKeyPressEvent(KeyEvent e) {
     392    }
     393
     394    private void doKeyReleaseEvent(KeyEvent e) {
     395        toggleDualAlign();
     396    }
     397
     398    /**
     399     * Toggles dual alignment mode.
     400     */
     401    private void toggleDualAlign() {
     402        dualAlignEnabled = !dualAlignEnabled;
     403        dualAlignCheckboxMenuItem.setState(dualAlignEnabled);
     404        updateStatusLine();
     405    }
     406
     407    /**
     408     * If the left mouse button is pressed over a segment or a node, switches
     409     * to appropriate {@link #mode}, depending on Ctrl/Alt/Shift modifiers and
     410     * {@link #dualAlignEnabled}.
     411     * @param e
     412     */
     413    @Override
     414    public void mousePressed(MouseEvent e) {
    247415        if(!Main.map.mapView.isActiveLayerVisible())
    248416            return;
     
    271439                }
    272440                mode = Mode.translate_node;
     441                dualAlignActive = false;
    273442            }
    274443        } else {
    275444            // Otherwise switch to another mode
     445            if (dualAlignEnabled && checkDualAlignConditions()) {
     446                dualAlignActive = true;
     447                calculatePossibleDirectionsForDualAlign();
     448            } else {
     449                dualAlignActive = false;
     450                calculatePossibleDirectionsBySegment();
     451            }
    276452            if (ctrl) {
    277453                mode = Mode.translate;
     
    289465                alwaysCreateNodes = shift;
    290466            }
    291             calculatePossibleDirectionsBySegment();
    292467        }
    293468
     
    296471        newN2en = null;
    297472        moveCommand = null;
     473        moveCommand2 = null;
    298474
    299475        Main.map.mapView.addTemporaryLayer(this);
     
    310486
    311487    /**
    312      * Perform action depending on what mode we're in.
    313      */
    314     @Override public void mouseDragged(MouseEvent e) {
     488     * Performs action depending on what {@link #mode} we're in.
     489     * @param e
     490     */
     491    @Override
     492    public void mouseDragged(MouseEvent e) {
    315493        if(!Main.map.mapView.isActiveLayerVisible())
    316494            return;
     
    327505            EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
    328506            EastNorth bestMovement = calculateBestMovement(mouseEn);
    329 
    330             newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
    331             newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
     507            EastNorth n1movedEn = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
    332508
    333509            // find out the movement distance, in metres
    334             double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(newN1en));
     510            double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n1movedEn));
    335511            Main.map.statusLine.setDist(distance);
    336512            updateStatusLine();
     
    338514            Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
    339515
    340             if (mode == Mode.extrude || mode == Mode.create_new) {
    341                 //nothing here
    342             } else if (mode == Mode.translate_node || mode == Mode.translate) {
    343                 //move nodes to new position
    344                 if (moveCommand == null) {
    345                     //make a new move command
    346                     moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY());
    347                     Main.main.undoRedo.add(moveCommand);
    348                 } else {
    349                     //reuse existing move command
    350                     moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY());
     516            if (dualAlignActive) {
     517                calculateDualAlignNodesPositions(bestMovement);
     518
     519                if (mode == Mode.extrude || mode == Mode.create_new) {
     520                    // nothing here
     521                } else if (mode == Mode.translate) {
     522                    EastNorth movement1 = initialN1en.sub(newN1en);
     523                    EastNorth movement2 = initialN2en.sub(newN2en);
     524                    // move nodes to new position
     525                    if (moveCommand == null || moveCommand2 == null) {
     526                        // make a new move commands
     527                        moveCommand = new MoveCommand(movingNodeList.get(0), movement1.getX(), movement1.getY());
     528                        moveCommand2 = new MoveCommand(movingNodeList.get(1), movement2.getX(), movement2.getY());
     529                        Command c = new SequenceCommand(tr("Extrude Way"), moveCommand, moveCommand2);
     530                        Main.main.undoRedo.add(c);
     531                    } else {
     532                        // reuse existing move commands
     533                        moveCommand.moveAgainTo(movement1.getX(), movement1.getY());
     534                        moveCommand2.moveAgainTo(movement2.getX(), movement2.getY());
     535                    }
    351536                }
     537            } else {
     538                newN1en = n1movedEn;
     539                newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
     540
     541                if (mode == Mode.extrude || mode == Mode.create_new) {
     542                    //nothing here
     543                } else if (mode == Mode.translate_node || mode == Mode.translate) {
     544                    //move nodes to new position
     545                    if (moveCommand == null) {
     546                        //make a new move command
     547                        moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY());
     548                        Main.main.undoRedo.add(moveCommand);
     549                    } else {
     550                        //reuse existing move command
     551                        moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY());
     552                    }
     553                }
    352554            }
    353555
     
    357559
    358560    /**
    359      * Do anything that needs to be done, then switch back to select mode
    360      */
    361     @Override public void mouseReleased(MouseEvent e) {
     561     * Does anything that needs to be done, then switches back to select mode.
     562     * @param e
     563     */
     564    @Override
     565    public void mouseReleased(MouseEvent e) {
    362566
    363567        if(!Main.map.mapView.isActiveLayerVisible())
     
    385589            }
    386590
    387             boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
    388             boolean ctrl = (e.getModifiers() & (ActionEvent.CTRL_MASK)) != 0;
    389             boolean shift = (e.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0;
     591            updateKeyModifiers(e);
    390592            // Switch back into select mode
    391593            Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this);
     
    400602    }
    401603
    402     /**
    403      * Insert node into nearby segment
    404      * @param e - current mouse point
     604    // -------------------------------------------------------------------------
     605    // Custom methods
     606    // -------------------------------------------------------------------------
     607
     608    /**
     609     * Inserts node into nearby segment.
     610     * @param e current mouse point
    405611     */
    406612    private void addNewNode(MouseEvent e) {
     
    420626    }
    421627
     628    /**
     629     * Creates a new way that shares segment with selected way.
     630     */
    422631    private void createNewRectangle() {
    423632        if (selectedSegment == null) return;
     
    443652
    444653    /**
    445      * Do actual extrusion of @field selectedSegment
     654     * Does actual extrusion of {@link #selectedSegment}.
    446655     */
    447656    private void performExtrusion() {
     
    458667        // segmentAngleZero marks subset of nodeOverlapsSegment. nodeOverlapsSegment is true if angle between segments is 0 or PI, segmentAngleZero only if angle is 0
    459668        boolean segmentAngleZero = prevNode != null && Math.abs(Geometry.getCornerAngle(prevNode.getEastNorth(), initialN1en, newN1en)) < 1e-5;
    460         boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);
     669        boolean hasOtherWays = hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);
    461670
    462671        if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
     
    522731
    523732    /**
    524      * This method tests if a node has other ways apart from the given one.
     733     * This method tests if {@code node} has other ways apart from the given one.
    525734     * @param node
    526735     * @param myWay
    527      * @return true of node belongs only to myWay, false if there are more ways.
    528      */
    529     private boolean hasNodeOtherWays(Node node, Way myWay) {
     736     * @return {@code true} if {@code node} belongs only to {@code myWay}, false if there are more ways.
     737     */
     738    private static boolean hasNodeOtherWays(Node node, Way myWay) {
    530739        for (OsmPrimitive p : node.getReferrers()) {
    531740            if (p instanceof Way && p.isUsable() && p != myWay)
     
    536745
    537746    /**
    538      * Determine best movenemnt from initialMousePos  to current position @param mouseEn,
    539      * choosing one of the directions @field possibleMoveDirections
     747     * Determines best movement from {@link #initialMousePos} to current mouse position,
     748     * choosing one of the directions from {@link #possibleMoveDirections}.
     749     * @param mouseEn current mouse position
    540750     * @return movement vector
    541751     */
     
    565775        }
    566776        return bestMovement;
     777
     778
    567779    }
    568780
    569781    /***
    570      * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position.
    571      * @param segmentP1
    572      * @param segmentP2
    573      * @param targetPos
     782     * This method calculates offset amount by which to move the given segment
     783     * perpendicularly for it to be in line with mouse position.
     784     * @param segmentP1 segment's first point
     785     * @param segmentP2 segment's second point
     786     * @param moveDirection direction of movement
     787     * @param targetPos mouse position
    574788     * @return offset amount of P1 and P2.
    575789     */
     
    591805
    592806    /**
    593      * Gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments
     807     * Gathers possible move directions - perpendicular to the selected segment
     808     * and parallel to neighboring segments.
    594809     */
    595810    private void calculatePossibleDirectionsBySegment() {
     
    627842
    628843    /**
    629      * Gather possible move directions - along all adjacent segments
     844     * Gathers possible move directions - along all adjacent segments.
    630845     */
    631846    private void calculatePossibleDirectionsByNode() {
     
    648863
    649864    /**
    650      * Gets a node from selected way before given index.
     865     * Checks dual alignment conditions:
     866     *  1. selected segment has both neighboring segments,
     867     *  2. selected segment is not parallel with neighboring segments.
     868     * @return {@code true} if dual alignment conditions are satisfied
     869     */
     870    private boolean checkDualAlignConditions() {
     871        Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     872        Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     873        if (prevNode == null || nextNode == null) {
     874            return false;
     875        }
     876
     877        EastNorth n1en = selectedSegment.getFirstNode().getEastNorth();
     878        EastNorth n2en = selectedSegment.getSecondNode().getEastNorth();
     879        boolean prevSegmentParallel = Geometry.segmentsParallel(n1en, prevNode.getEastNorth(), n1en, n2en);
     880        boolean nextSegmentParallel = Geometry.segmentsParallel(n2en, nextNode.getEastNorth(), n1en, n2en);
     881        if (prevSegmentParallel || nextSegmentParallel) {
     882            return false;
     883        }
     884
     885        return true;
     886    }
     887
     888    /**
     889     * Gathers possible move directions - perpendicular to the selected segment only.
     890     * Neighboring segments go to {@link #dualAlignSegment1} and {@link #dualAlignSegment2}.
     891     */
     892    private void calculatePossibleDirectionsForDualAlign() {
     893        // remember initial positions for segment nodes.
     894        initialN1en = selectedSegment.getFirstNode().getEastNorth();
     895        initialN2en = selectedSegment.getSecondNode().getEastNorth();
     896
     897        // add direction perpendicular to the selected segment
     898        possibleMoveDirections = new ArrayList<ReferenceSegment>();
     899        possibleMoveDirections.add(new ReferenceSegment(new EastNorth(
     900                initialN1en.getY() - initialN2en.getY(),
     901                initialN2en.getX() - initialN1en.getX()
     902                ), initialN1en, initialN2en, true));
     903
     904        // set neighboring segments
     905        Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     906        EastNorth prevNodeEn = prevNode.getEastNorth();
     907        dualAlignSegment1 = new ReferenceSegment(new EastNorth(
     908            initialN1en.getX() - prevNodeEn.getX(),
     909            initialN1en.getY() - prevNodeEn.getY()
     910            ), initialN1en, prevNodeEn, false);
     911
     912        Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     913        EastNorth nextNodeEn = nextNode.getEastNorth();
     914        dualAlignSegment2 = new ReferenceSegment(new EastNorth(
     915            initialN2en.getX() - nextNodeEn.getX(),
     916            initialN2en.getY() - nextNodeEn.getY()
     917            ), initialN2en,  nextNodeEn, false);
     918    }
     919
     920    /**
     921     * Calculates positions of new nodes, aligning them to neighboring segments.
     922     * @param movement movement to be used
     923     */
     924    private void calculateDualAlignNodesPositions(EastNorth movement) {
     925        // new positions of selected segment's nodes, without applying dual alignment
     926        EastNorth n1movedEn = new EastNorth(initialN1en.getX() + movement.getX(), initialN1en.getY() + movement.getY());
     927        EastNorth n2movedEn = new EastNorth(initialN2en.getX() + movement.getX(), initialN2en.getY() + movement.getY());
     928
     929        // calculate intersections
     930        newN1en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment1.p1, dualAlignSegment1.p2);
     931        newN2en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment2.p1, dualAlignSegment2.p2);
     932    }
     933
     934    /**
     935     * Gets a node index from selected way before given index.
    651936     * @param index  index of current node
    652      * @return index of previous node or -1 if there are no nodes there.
     937     * @return index of previous node or <code>-1</code> if there are no nodes there.
    653938     */
    654939    private int getPreviousNodeIndex(int index) {
     
    664949     * Gets a node from selected way before given index.
    665950     * @param index  index of current node
    666      * @return previous node or null if there are no nodes there.
     951     * @return previous node or <code>null</code> if there are no nodes there.
    667952     */
    668953    private Node getPreviousNode(int index) {
     
    676961
    677962    /**
    678      * Gets a node from selected way after given index.
     963     * Gets a node index from selected way after given index.
    679964     * @param index index of current node
    680      * @return index of next node or -1 if there are no nodes there.
     965     * @return index of next node or <code>-1</code> if there are no nodes there.
    681966     */
    682967    private int getNextNodeIndex(int index) {
     
    693978     * Gets a node from selected way after given index.
    694979     * @param index index of current node
    695      * @return next node or null if there are no nodes there.
     980     * @return next node or <code>null</code> if there are no nodes there.
    696981     */
    697982    private Node getNextNode(int index) {
     
    702987            return null;
    703988    }
     989
     990    // -------------------------------------------------------------------------
     991    // paint methods
     992    // -------------------------------------------------------------------------
    704993
    705994    @Override
     
    7281017                    g2.draw(b);
    7291018
    730                     if (activeMoveDirection != null) {
     1019                    if (dualAlignActive) {
     1020                        // Draw reference ways
     1021                        drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2);
     1022                        drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2);
     1023                    } else if (activeMoveDirection != null) {
    7311024                        // Draw reference way
    732                         Point pr1 = mv.getPoint(activeMoveDirection.p1);
    733                         Point pr2 = mv.getPoint(activeMoveDirection.p2);
    734                         b = new GeneralPath();
    735                         b.moveTo(pr1.x, pr1.y);
    736                         b.lineTo(pr2.x, pr2.y);
    737                         g2.setColor(helperColor);
    738                         g2.setStroke(helperStrokeDash);
    739                         g2.draw(b);
     1025                        drawReferenceSegment(g2, mv, activeMoveDirection.p1, activeMoveDirection.p2);
    7401026
    7411027                        // Draw right angle marker on first node position, only when moving at right angle
     
    7471033                            if (headingDiff < 0) headingDiff += 2 * Math.PI;
    7481034                            boolean mirrorRA = Math.abs(headingDiff - Math.PI) > 1e-5;
     1035                            Point pr1 = mv.getPoint(activeMoveDirection.p1);
    7491036                            drawAngleSymbol(g2, pr1, normalUnitVector, mirrorRA);
    7501037                        }
     
    7621049                    }
    7631050
    764                     if (activeMoveDirection != null) {
     1051                    if (dualAlignActive) {
     1052                        // Draw reference ways
     1053                        drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2);
     1054                        drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2);
     1055                    } else if (activeMoveDirection != null) {
    7651056
    7661057                        g2.setColor(helperColor);
     
    8031094    }
    8041095
     1096    /**
     1097     * Draws right angle symbol at specified position.
     1098     * @param g2 the Graphics2D object used to draw on
     1099     * @param center center point of angle
     1100     * @param normal vector of normal
     1101     * @param mirror {@code true} if symbol should be mirrored by the normal
     1102     */
    8051103    private void drawAngleSymbol(Graphics2D g2, Point2D center, Point2D normal, boolean mirror) {
    8061104        // EastNorth units per pixel
     
    8241122
    8251123    /**
    826      * Create a new Line that extends off the edge of the viewport in one direction
     1124     * Draws given reference segment.
     1125     * @param g2 the Graphics2D object used to draw on
     1126     * @param mv
     1127     * @param p1en segment's first point
     1128     * @param p2en segment's second point
     1129     */
     1130    private void drawReferenceSegment(Graphics2D g2, MapView mv, EastNorth p1en, EastNorth p2en)
     1131    {
     1132        Point p1 = mv.getPoint(p1en);
     1133        Point p2 = mv.getPoint(p2en);
     1134        GeneralPath b = new GeneralPath();
     1135        b.moveTo(p1.x, p1.y);
     1136        b.lineTo(p2.x, p2.y);
     1137        g2.setColor(helperColor);
     1138        g2.setStroke(helperStrokeDash);
     1139        g2.draw(b);
     1140    }
     1141
     1142    /**
     1143     * Creates a new Line that extends off the edge of the viewport in one direction
    8271144     * @param start The start point of the line
    8281145     * @param unitvector A unit vector denoting the direction of the line
    8291146     * @param g the Graphics2D object  it will be used on
     1147     * @return created line
    8301148     */
    8311149    private static Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
Note: See TracChangeset for help on using the changeset viewer.