Ignore:
Timestamp:
2017-01-25T14:51:50+01:00 (7 years ago)
Author:
Don-vip
Message:

extract DrawAction.SnapHelper to a new class

File:
1 edited

Legend:

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

    r11452 r11495  
    1515import java.awt.event.KeyEvent;
    1616import java.awt.event.MouseEvent;
    17 import java.awt.event.MouseListener;
    1817import java.util.ArrayList;
    19 import java.util.Arrays;
    2018import java.util.Collection;
    2119import java.util.Collections;
    22 import java.util.Comparator;
    2320import java.util.HashMap;
    2421import java.util.HashSet;
     
    2825import java.util.Map;
    2926import java.util.Set;
    30 import java.util.stream.DoubleStream;
    3127
    3228import javax.swing.AbstractAction;
     
    3430import javax.swing.JMenuItem;
    3531import javax.swing.JOptionPane;
    36 import javax.swing.JPopupMenu;
    3732
    3833import org.openstreetmap.josm.Main;
     
    4540import org.openstreetmap.josm.data.SelectionChangedListener;
    4641import org.openstreetmap.josm.data.coor.EastNorth;
    47 import org.openstreetmap.josm.data.coor.LatLon;
    4842import org.openstreetmap.josm.data.osm.DataSet;
    4943import org.openstreetmap.josm.data.osm.Node;
     
    6256import org.openstreetmap.josm.gui.MapFrame;
    6357import org.openstreetmap.josm.gui.MapView;
    64 import org.openstreetmap.josm.gui.MapViewState;
    6558import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    6659import org.openstreetmap.josm.gui.NavigatableComponent;
    6760import org.openstreetmap.josm.gui.draw.MapPath2D;
    68 import org.openstreetmap.josm.gui.draw.MapViewPath;
    69 import org.openstreetmap.josm.gui.draw.SymbolShape;
    7061import org.openstreetmap.josm.gui.layer.Layer;
    7162import org.openstreetmap.josm.gui.layer.MapViewPaintable;
     
    7364import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
    7465import org.openstreetmap.josm.gui.util.ModifierListener;
    75 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    7666import org.openstreetmap.josm.tools.Geometry;
    7767import org.openstreetmap.josm.tools.ImageProvider;
     
    8979    private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8);
    9080
    91     private static final CachingProperty<Boolean> USE_REPEATED_SHORTCUT
     81    static final CachingProperty<Boolean> USE_REPEATED_SHORTCUT
    9282            = new BooleanProperty("draw.anglesnap.toggleOnRepeatedA", true).cached();
    93     private static final CachingProperty<BasicStroke> RUBBER_LINE_STROKE
     83    static final CachingProperty<BasicStroke> RUBBER_LINE_STROKE
    9484            = new StrokeProperty("draw.stroke.helper-line", "3").cached();
    9585
    96     private static final CachingProperty<BasicStroke> HIGHLIGHT_STROKE
     86    static final CachingProperty<BasicStroke> HIGHLIGHT_STROKE
    9787            = new StrokeProperty("draw.anglesnap.stroke.highlight", "10").cached();
    98     private static final CachingProperty<BasicStroke> HELPER_STROKE
     88    static final CachingProperty<BasicStroke> HELPER_STROKE
    9989            = new StrokeProperty("draw.anglesnap.stroke.helper", "1 4").cached();
    10090
    101     private static final CachingProperty<Double> SNAP_ANGLE_TOLERANCE
     91    static final CachingProperty<Double> SNAP_ANGLE_TOLERANCE
    10292            = new DoubleProperty("draw.anglesnap.tolerance", 5.0).cached();
    103     private static final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY
     93    static final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY
    10494            = new BooleanProperty("draw.anglesnap.drawConstructionGeometry", true).cached();
    105     private static final CachingProperty<Boolean> SHOW_PROJECTED_POINT
     95    static final CachingProperty<Boolean> SHOW_PROJECTED_POINT
    10696            = new BooleanProperty("draw.anglesnap.drawProjectedPoint", true).cached();
    107     private static final CachingProperty<Boolean> SNAP_TO_PROJECTIONS
     97    static final CachingProperty<Boolean> SNAP_TO_PROJECTIONS
    10898            = new BooleanProperty("draw.anglesnap.projectionsnap", true).cached();
    10999
    110     private static final CachingProperty<Boolean> SHOW_ANGLE
     100    static final CachingProperty<Boolean> SHOW_ANGLE
    111101            = new BooleanProperty("draw.anglesnap.showAngle", true).cached();
    112102
    113     private static final CachingProperty<Color> SNAP_HELPER_COLOR
     103    static final CachingProperty<Color> SNAP_HELPER_COLOR
    114104            = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).cached();
    115105
    116     private static final CachingProperty<Color> HIGHLIGHT_COLOR
     106    static final CachingProperty<Color> HIGHLIGHT_COLOR
    117107            = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached();
    118108
    119     private static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR
     109    static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR
    120110            = PaintColors.SELECTED.getProperty().getChildColor(marktr("helper line"));
    121111
    122     private static final CachingProperty<Boolean> DRAW_HELPER_LINE
     112    static final CachingProperty<Boolean> DRAW_HELPER_LINE
    123113            = new BooleanProperty("draw.helper-line", true).cached();
    124     private static final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT
     114    static final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT
    125115            = new BooleanProperty("draw.target-highlight", true).cached();
    126     private static final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD
     116    static final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD
    127117            = new DoubleProperty("edit.snap-intersection-threshold", 10).cached();
    128118
     
    151141    private EastNorth currentMouseEastNorth;
    152142
    153     private final transient SnapHelper snapHelper = new SnapHelper();
     143    private final transient DrawSnapHelper snapHelper = new DrawSnapHelper(this);
    154144
    155145    private final transient Shortcut backspaceShortcut;
     
    873863    }
    874864
    875     private static void showStatusInfo(double angle, double hdg, double distance, boolean activeFlag) {
     865    static void showStatusInfo(double angle, double hdg, double distance, boolean activeFlag) {
    876866        Main.map.statusLine.setAngle(angle);
    877867        Main.map.statusLine.activateAnglePanel(activeFlag);
     
    13421332    }
    13431333
    1344     private class SnapHelper {
    1345         private static final String DRAW_ANGLESNAP_ANGLES = "draw.anglesnap.angles";
    1346 
    1347         private final class AnglePopupMenu extends JPopupMenu {
    1348 
    1349             private final JCheckBoxMenuItem repeatedCb = new JCheckBoxMenuItem(
    1350                     new AbstractAction(tr("Toggle snapping by {0}", getShortcut().getKeyText())) {
    1351                 @Override
    1352                 public void actionPerformed(ActionEvent e) {
    1353                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1354                     USE_REPEATED_SHORTCUT.put(sel);
    1355                 }
    1356             });
    1357 
    1358             private final JCheckBoxMenuItem helperCb = new JCheckBoxMenuItem(
    1359                     new AbstractAction(tr("Show helper geometry")) {
    1360                 @Override
    1361                 public void actionPerformed(ActionEvent e) {
    1362                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1363                     DRAW_CONSTRUCTION_GEOMETRY.put(sel);
    1364                     SHOW_PROJECTED_POINT.put(sel);
    1365                     SHOW_ANGLE.put(sel);
    1366                     enableSnapping();
    1367                 }
    1368             });
    1369 
    1370             private final JCheckBoxMenuItem projectionCb = new JCheckBoxMenuItem(
    1371                     new AbstractAction(tr("Snap to node projections")) {
    1372                 @Override
    1373                 public void actionPerformed(ActionEvent e) {
    1374                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1375                     SNAP_TO_PROJECTIONS.put(sel);
    1376                     enableSnapping();
    1377                 }
    1378             });
    1379 
    1380             private AnglePopupMenu() {
    1381                 helperCb.setState(DRAW_CONSTRUCTION_GEOMETRY.get());
    1382                 projectionCb.setState(SNAP_TO_PROJECTIONS.get());
    1383                 repeatedCb.setState(USE_REPEATED_SHORTCUT.get());
    1384                 add(repeatedCb);
    1385                 add(helperCb);
    1386                 add(projectionCb);
    1387                 add(new AbstractAction(tr("Disable")) {
    1388                     @Override public void actionPerformed(ActionEvent e) {
    1389                         saveAngles("180");
    1390                         init();
    1391                         enableSnapping();
    1392                     }
    1393                 });
    1394                 add(new AbstractAction(tr("0,90,...")) {
    1395                     @Override public void actionPerformed(ActionEvent e) {
    1396                         saveAngles("0", "90", "180");
    1397                         init();
    1398                         enableSnapping();
    1399                     }
    1400                 });
    1401                 add(new AbstractAction(tr("0,45,90,...")) {
    1402                     @Override public void actionPerformed(ActionEvent e) {
    1403                         saveAngles("0", "45", "90", "135", "180");
    1404                         init();
    1405                         enableSnapping();
    1406                     }
    1407                 });
    1408                 add(new AbstractAction(tr("0,30,45,60,90,...")) {
    1409                     @Override public void actionPerformed(ActionEvent e) {
    1410                         saveAngles("0", "30", "45", "60", "90", "120", "135", "150", "180");
    1411                         init();
    1412                         enableSnapping();
    1413                     }
    1414                 });
    1415             }
    1416         }
    1417 
    1418         private boolean snapOn; // snapping is turned on
    1419 
    1420         private boolean active; // snapping is active for current mouse position
    1421         private boolean fixed; // snap angle is fixed
    1422         private boolean absoluteFix; // snap angle is absolute
    1423 
    1424         private EastNorth dir2;
    1425         private EastNorth projected;
    1426         private String labelText;
    1427         private double lastAngle;
    1428 
    1429         private double customBaseHeading = -1; // angle of base line, if not last segment)
    1430         private EastNorth segmentPoint1; // remembered first point of base segment
    1431         private EastNorth segmentPoint2; // remembered second point of base segment
    1432         private EastNorth projectionSource; // point that we are projecting to the line
    1433 
    1434         private double[] snapAngles;
    1435 
    1436         private double pe, pn; // (pe, pn) - direction of snapping line
    1437         private double e0, n0; // (e0, n0) - origin of snapping line
    1438 
    1439         private final String fixFmt = "%d "+tr("FIX");
    1440 
    1441         private JCheckBoxMenuItem checkBox;
    1442 
    1443         private final MouseListener anglePopupListener = new PopupMenuLauncher(new AnglePopupMenu()) {
    1444             @Override
    1445             public void mouseClicked(MouseEvent e) {
    1446                 super.mouseClicked(e);
    1447                 if (e.getButton() == MouseEvent.BUTTON1) {
    1448                     toggleSnapping();
    1449                     updateStatusLine();
    1450                 }
    1451             }
    1452         };
    1453 
    1454         /**
    1455          * Set the initial state
    1456          */
    1457         public void init() {
    1458             snapOn = false;
    1459             checkBox.setState(snapOn);
    1460             fixed = false;
    1461             absoluteFix = false;
    1462 
    1463             computeSnapAngles();
    1464             Main.pref.addWeakKeyPreferenceChangeListener(DRAW_ANGLESNAP_ANGLES, e -> this.computeSnapAngles());
    1465         }
    1466 
    1467         private void computeSnapAngles() {
    1468             snapAngles = Main.pref.getCollection(DRAW_ANGLESNAP_ANGLES,
    1469                     Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"))
    1470                     .stream()
    1471                     .mapToDouble(this::parseSnapAngle)
    1472                     .flatMap(s -> DoubleStream.of(s, 360-s))
    1473                     .toArray();
    1474         }
    1475 
    1476         private double parseSnapAngle(String string) {
    1477             try {
    1478                 return Double.parseDouble(string);
    1479             } catch (NumberFormatException e) {
    1480                 Main.warn("Incorrect number in draw.anglesnap.angles preferences: {0}", string);
    1481                 return 0;
    1482             }
    1483         }
    1484 
    1485         /**
    1486          * Save the snap angles
    1487          * @param angles The angles
    1488          */
    1489         public void saveAngles(String ... angles) {
    1490             Main.pref.putCollection(DRAW_ANGLESNAP_ANGLES, Arrays.asList(angles));
    1491         }
    1492 
    1493         public void setMenuCheckBox(JCheckBoxMenuItem checkBox) {
    1494             this.checkBox = checkBox;
    1495         }
    1496 
    1497         /**
    1498          * Draw the snap hint line.
    1499          * @param g2 graphics
    1500          * @param mv MapView state
    1501          * @since 10874
    1502          */
    1503         public void drawIfNeeded(Graphics2D g2, MapViewState mv) {
    1504             if (!snapOn || !active)
    1505                 return;
    1506             MapViewPoint p1 = mv.getPointFor(getCurrentBaseNode());
    1507             MapViewPoint p2 = mv.getPointFor(dir2);
    1508             MapViewPoint p3 = mv.getPointFor(projected);
    1509             if (DRAW_CONSTRUCTION_GEOMETRY.get()) {
    1510                 g2.setColor(SNAP_HELPER_COLOR.get());
    1511                 g2.setStroke(HELPER_STROKE.get());
    1512 
    1513                 MapViewPath b = new MapViewPath(mv);
    1514                 b.moveTo(p2);
    1515                 if (absoluteFix) {
    1516                     b.lineTo(p2.interpolate(p1, 2)); // bi-directional line
    1517                 } else {
    1518                     b.lineTo(p3);
    1519                 }
    1520                 g2.draw(b);
    1521             }
    1522             if (projectionSource != null) {
    1523                 g2.setColor(SNAP_HELPER_COLOR.get());
    1524                 g2.setStroke(HELPER_STROKE.get());
    1525                 MapViewPath b = new MapViewPath(mv);
    1526                 b.moveTo(p3);
    1527                 b.lineTo(projectionSource);
    1528                 g2.draw(b);
    1529             }
    1530 
    1531             if (customBaseHeading >= 0) {
    1532                 g2.setColor(HIGHLIGHT_COLOR.get());
    1533                 g2.setStroke(HIGHLIGHT_STROKE.get());
    1534                 MapViewPath b = new MapViewPath(mv);
    1535                 b.moveTo(segmentPoint1);
    1536                 b.lineTo(segmentPoint2);
    1537                 g2.draw(b);
    1538             }
    1539 
    1540             g2.setColor(RUBBER_LINE_COLOR.get());
    1541             g2.setStroke(RUBBER_LINE_STROKE.get());
    1542             MapViewPath b = new MapViewPath(mv);
    1543             b.moveTo(p1);
    1544             b.lineTo(p3);
    1545             g2.draw(b);
    1546 
    1547             g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
    1548             if (SHOW_PROJECTED_POINT.get()) {
    1549                 g2.setStroke(RUBBER_LINE_STROKE.get());
    1550                 g2.draw(new MapViewPath(mv).shapeAround(p3, SymbolShape.CIRCLE, 10)); // projected point
    1551             }
    1552 
    1553             g2.setColor(SNAP_HELPER_COLOR.get());
    1554             g2.setStroke(HELPER_STROKE.get());
    1555         }
    1556 
    1557         /**
    1558          * If mouse position is close to line at 15-30-45-... angle, remembers this direction
    1559          * @param currentEN Current position
    1560          * @param baseHeading The heading
    1561          * @param curHeading The current mouse heading
    1562          */
    1563         public void checkAngleSnapping(EastNorth currentEN, double baseHeading, double curHeading) {
    1564             EastNorth p0 = getCurrentBaseNode().getEastNorth();
    1565             EastNorth snapPoint = currentEN;
    1566             double angle = -1;
    1567 
    1568             double activeBaseHeading = (customBaseHeading >= 0) ? customBaseHeading : baseHeading;
    1569 
    1570             if (snapOn && (activeBaseHeading >= 0)) {
    1571                 angle = curHeading - activeBaseHeading;
    1572                 if (angle < 0) {
    1573                     angle += 360;
    1574                 }
    1575                 if (angle > 360) {
    1576                     angle = 0;
    1577                 }
    1578 
    1579                 double nearestAngle;
    1580                 if (fixed) {
    1581                     nearestAngle = lastAngle; // if direction is fixed use previous angle
    1582                     active = true;
    1583                 } else {
    1584                     nearestAngle = getNearestAngle(angle);
    1585                     if (getAngleDelta(nearestAngle, angle) < SNAP_ANGLE_TOLERANCE.get()) {
    1586                         active = customBaseHeading >= 0 || Math.abs(nearestAngle - 180) > 1e-3;
    1587                         // if angle is to previous segment, exclude 180 degrees
    1588                         lastAngle = nearestAngle;
    1589                     } else {
    1590                         active = false;
    1591                     }
    1592                 }
    1593 
    1594                 if (active) {
    1595                     double phi;
    1596                     e0 = p0.east();
    1597                     n0 = p0.north();
    1598                     buildLabelText((nearestAngle <= 180) ? nearestAngle : (nearestAngle-360));
    1599 
    1600                     phi = (nearestAngle + activeBaseHeading) * Math.PI / 180;
    1601                     // (pe,pn) - direction of snapping line
    1602                     pe = Math.sin(phi);
    1603                     pn = Math.cos(phi);
    1604                     double scale = 20 * Main.map.mapView.getDist100Pixel();
    1605                     dir2 = new EastNorth(e0 + scale * pe, n0 + scale * pn);
    1606                     snapPoint = getSnapPoint(currentEN);
    1607                 } else {
    1608                     noSnapNow();
    1609                 }
    1610             }
    1611 
    1612             // find out the distance, in metres, between the base point and projected point
    1613             LatLon mouseLatLon = Main.map.mapView.getProjection().eastNorth2latlon(snapPoint);
    1614             double distance = getCurrentBaseNode().getCoor().greatCircleDistance(mouseLatLon);
    1615             double hdg = Math.toDegrees(p0.heading(snapPoint));
    1616             // heading of segment from current to calculated point, not to mouse position
    1617 
    1618             if (baseHeading >= 0) { // there is previous line segment with some heading
    1619                 angle = hdg - baseHeading;
    1620                 if (angle < 0) {
    1621                     angle += 360;
    1622                 }
    1623                 if (angle > 360) {
    1624                     angle = 0;
    1625                 }
    1626             }
    1627             showStatusInfo(angle, hdg, distance, isSnapOn());
    1628         }
    1629 
    1630         private void buildLabelText(double nearestAngle) {
    1631             if (SHOW_ANGLE.get()) {
    1632                 if (fixed) {
    1633                     if (absoluteFix) {
    1634                         labelText = "=";
    1635                     } else {
    1636                         labelText = String.format(fixFmt, (int) nearestAngle);
    1637                     }
    1638                 } else {
    1639                     labelText = String.format("%d", (int) nearestAngle);
    1640                 }
    1641             } else {
    1642                 if (fixed) {
    1643                     if (absoluteFix) {
    1644                         labelText = "=";
    1645                     } else {
    1646                         labelText = String.format(tr("FIX"), 0);
    1647                     }
    1648                 } else {
    1649                     labelText = "";
    1650                 }
    1651             }
    1652         }
    1653 
    1654         /**
    1655          * Gets a snap point close to p. Stores the result for display.
    1656          * @param p The point
    1657          * @return The snap point close to p.
    1658          */
    1659         public EastNorth getSnapPoint(EastNorth p) {
    1660             if (!active)
    1661                 return p;
    1662             double de = p.east()-e0;
    1663             double dn = p.north()-n0;
    1664             double l = de*pe+dn*pn;
    1665             double delta = Main.map.mapView.getDist100Pixel()/20;
    1666             if (!absoluteFix && l < delta) {
    1667                 active = false;
    1668                 return p;
    1669             } //  do not go backward!
    1670 
    1671             projectionSource = null;
    1672             if (SNAP_TO_PROJECTIONS.get()) {
    1673                 DataSet ds = getLayerManager().getEditDataSet();
    1674                 Collection<Way> selectedWays = ds.getSelectedWays();
    1675                 if (selectedWays.size() == 1) {
    1676                     Way w = selectedWays.iterator().next();
    1677                     Collection<EastNorth> pointsToProject = new ArrayList<>();
    1678                     if (w.getNodesCount() < 1000) {
    1679                         for (Node n: w.getNodes()) {
    1680                             pointsToProject.add(n.getEastNorth());
    1681                         }
    1682                     }
    1683                     if (customBaseHeading >= 0) {
    1684                         pointsToProject.add(segmentPoint1);
    1685                         pointsToProject.add(segmentPoint2);
    1686                     }
    1687                     EastNorth enOpt = null;
    1688                     double dOpt = 1e5;
    1689                     for (EastNorth en: pointsToProject) { // searching for besht projection
    1690                         double l1 = (en.east()-e0)*pe+(en.north()-n0)*pn;
    1691                         double d1 = Math.abs(l1-l);
    1692                         if (d1 < delta && d1 < dOpt) {
    1693                             l = l1;
    1694                             enOpt = en;
    1695                             dOpt = d1;
    1696                         }
    1697                     }
    1698                     if (enOpt != null) {
    1699                         projectionSource = enOpt;
    1700                     }
    1701                 }
    1702             }
    1703             projected = new EastNorth(e0+l*pe, n0+l*pn);
    1704             return projected;
    1705         }
    1706 
    1707         /**
    1708          * Disables snapping
    1709          */
    1710         public void noSnapNow() {
    1711             active = false;
    1712             dir2 = null;
    1713             projected = null;
    1714             labelText = null;
    1715         }
    1716 
    1717         public void setBaseSegment(WaySegment seg) {
    1718             if (seg == null) return;
    1719             segmentPoint1 = seg.getFirstNode().getEastNorth();
    1720             segmentPoint2 = seg.getSecondNode().getEastNorth();
    1721 
    1722             double hdg = segmentPoint1.heading(segmentPoint2);
    1723             hdg = Math.toDegrees(hdg);
    1724             if (hdg < 0) {
    1725                 hdg += 360;
    1726             }
    1727             if (hdg > 360) {
    1728                 hdg -= 360;
    1729             }
    1730             customBaseHeading = hdg;
    1731         }
    1732 
    1733         /**
    1734          * Enable snapping.
    1735          */
    1736         private void enableSnapping() {
    1737             snapOn = true;
    1738             checkBox.setState(snapOn);
    1739             customBaseHeading = -1;
    1740             unsetFixedMode();
    1741         }
    1742 
    1743         private void toggleSnapping() {
    1744             snapOn = !snapOn;
    1745             checkBox.setState(snapOn);
    1746             customBaseHeading = -1;
    1747             unsetFixedMode();
    1748         }
    1749 
    1750         public void setFixedMode() {
    1751             if (active) {
    1752                 fixed = true;
    1753             }
    1754         }
    1755 
    1756         public void unsetFixedMode() {
    1757             fixed = false;
    1758             absoluteFix = false;
    1759             lastAngle = 0;
    1760             active = false;
    1761         }
    1762 
    1763         public boolean isActive() {
    1764             return active;
    1765         }
    1766 
    1767         public boolean isSnapOn() {
    1768             return snapOn;
    1769         }
    1770 
    1771         private double getNearestAngle(double angle) {
    1772             double bestAngle = DoubleStream.of(snapAngles).boxed()
    1773                     .min(Comparator.comparing(snapAngle -> getAngleDelta(angle, snapAngle))).orElse(0.0);
    1774             if (Math.abs(bestAngle-360) < 1e-3) {
    1775                 bestAngle = 0;
    1776             }
    1777             return bestAngle;
    1778         }
    1779 
    1780         private double getAngleDelta(double a, double b) {
    1781             double delta = Math.abs(a-b);
    1782             if (delta > 180)
    1783                 return 360-delta;
    1784             else
    1785                 return delta;
    1786         }
    1787 
    1788         private void unFixOrTurnOff() {
    1789             if (absoluteFix) {
    1790                 unsetFixedMode();
    1791             } else {
    1792                 toggleSnapping();
    1793             }
    1794         }
    1795     }
    1796 
    17971334    private class SnapChangeAction extends JosmAction {
    17981335        /**
Note: See TracChangeset for help on using the changeset viewer.