Changeset 11495 in josm
- Timestamp:
- 2017-01-25T14:51:50+01:00 (8 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/actions/mapmode
- Files:
-
- 1 added
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
r11452 r11495 15 15 import java.awt.event.KeyEvent; 16 16 import java.awt.event.MouseEvent; 17 import java.awt.event.MouseListener;18 17 import java.util.ArrayList; 19 import java.util.Arrays;20 18 import java.util.Collection; 21 19 import java.util.Collections; 22 import java.util.Comparator;23 20 import java.util.HashMap; 24 21 import java.util.HashSet; … … 28 25 import java.util.Map; 29 26 import java.util.Set; 30 import java.util.stream.DoubleStream;31 27 32 28 import javax.swing.AbstractAction; … … 34 30 import javax.swing.JMenuItem; 35 31 import javax.swing.JOptionPane; 36 import javax.swing.JPopupMenu;37 32 38 33 import org.openstreetmap.josm.Main; … … 45 40 import org.openstreetmap.josm.data.SelectionChangedListener; 46 41 import org.openstreetmap.josm.data.coor.EastNorth; 47 import org.openstreetmap.josm.data.coor.LatLon;48 42 import org.openstreetmap.josm.data.osm.DataSet; 49 43 import org.openstreetmap.josm.data.osm.Node; … … 62 56 import org.openstreetmap.josm.gui.MapFrame; 63 57 import org.openstreetmap.josm.gui.MapView; 64 import org.openstreetmap.josm.gui.MapViewState;65 58 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 66 59 import org.openstreetmap.josm.gui.NavigatableComponent; 67 60 import org.openstreetmap.josm.gui.draw.MapPath2D; 68 import org.openstreetmap.josm.gui.draw.MapViewPath;69 import org.openstreetmap.josm.gui.draw.SymbolShape;70 61 import org.openstreetmap.josm.gui.layer.Layer; 71 62 import org.openstreetmap.josm.gui.layer.MapViewPaintable; … … 73 64 import org.openstreetmap.josm.gui.util.KeyPressReleaseListener; 74 65 import org.openstreetmap.josm.gui.util.ModifierListener; 75 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;76 66 import org.openstreetmap.josm.tools.Geometry; 77 67 import org.openstreetmap.josm.tools.ImageProvider; … … 89 79 private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8); 90 80 91 privatestatic final CachingProperty<Boolean> USE_REPEATED_SHORTCUT81 static final CachingProperty<Boolean> USE_REPEATED_SHORTCUT 92 82 = new BooleanProperty("draw.anglesnap.toggleOnRepeatedA", true).cached(); 93 privatestatic final CachingProperty<BasicStroke> RUBBER_LINE_STROKE83 static final CachingProperty<BasicStroke> RUBBER_LINE_STROKE 94 84 = new StrokeProperty("draw.stroke.helper-line", "3").cached(); 95 85 96 privatestatic final CachingProperty<BasicStroke> HIGHLIGHT_STROKE86 static final CachingProperty<BasicStroke> HIGHLIGHT_STROKE 97 87 = new StrokeProperty("draw.anglesnap.stroke.highlight", "10").cached(); 98 privatestatic final CachingProperty<BasicStroke> HELPER_STROKE88 static final CachingProperty<BasicStroke> HELPER_STROKE 99 89 = new StrokeProperty("draw.anglesnap.stroke.helper", "1 4").cached(); 100 90 101 privatestatic final CachingProperty<Double> SNAP_ANGLE_TOLERANCE91 static final CachingProperty<Double> SNAP_ANGLE_TOLERANCE 102 92 = new DoubleProperty("draw.anglesnap.tolerance", 5.0).cached(); 103 privatestatic final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY93 static final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY 104 94 = new BooleanProperty("draw.anglesnap.drawConstructionGeometry", true).cached(); 105 privatestatic final CachingProperty<Boolean> SHOW_PROJECTED_POINT95 static final CachingProperty<Boolean> SHOW_PROJECTED_POINT 106 96 = new BooleanProperty("draw.anglesnap.drawProjectedPoint", true).cached(); 107 privatestatic final CachingProperty<Boolean> SNAP_TO_PROJECTIONS97 static final CachingProperty<Boolean> SNAP_TO_PROJECTIONS 108 98 = new BooleanProperty("draw.anglesnap.projectionsnap", true).cached(); 109 99 110 privatestatic final CachingProperty<Boolean> SHOW_ANGLE100 static final CachingProperty<Boolean> SHOW_ANGLE 111 101 = new BooleanProperty("draw.anglesnap.showAngle", true).cached(); 112 102 113 privatestatic final CachingProperty<Color> SNAP_HELPER_COLOR103 static final CachingProperty<Color> SNAP_HELPER_COLOR 114 104 = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).cached(); 115 105 116 privatestatic final CachingProperty<Color> HIGHLIGHT_COLOR106 static final CachingProperty<Color> HIGHLIGHT_COLOR 117 107 = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached(); 118 108 119 privatestatic final AbstractToStringProperty<Color> RUBBER_LINE_COLOR109 static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR 120 110 = PaintColors.SELECTED.getProperty().getChildColor(marktr("helper line")); 121 111 122 privatestatic final CachingProperty<Boolean> DRAW_HELPER_LINE112 static final CachingProperty<Boolean> DRAW_HELPER_LINE 123 113 = new BooleanProperty("draw.helper-line", true).cached(); 124 privatestatic final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT114 static final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT 125 115 = new BooleanProperty("draw.target-highlight", true).cached(); 126 privatestatic final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD116 static final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD 127 117 = new DoubleProperty("edit.snap-intersection-threshold", 10).cached(); 128 118 … … 151 141 private EastNorth currentMouseEastNorth; 152 142 153 private final transient SnapHelper snapHelper = new SnapHelper(); 143 private final transient DrawSnapHelper snapHelper = new DrawSnapHelper(this); 154 144 155 145 private final transient Shortcut backspaceShortcut; … … 873 863 } 874 864 875 privatestatic void showStatusInfo(double angle, double hdg, double distance, boolean activeFlag) {865 static void showStatusInfo(double angle, double hdg, double distance, boolean activeFlag) { 876 866 Main.map.statusLine.setAngle(angle); 877 867 Main.map.statusLine.activateAnglePanel(activeFlag); … … 1342 1332 } 1343 1333 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 @Override1352 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 @Override1361 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 @Override1373 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 on1419 1420 private boolean active; // snapping is active for current mouse position1421 private boolean fixed; // snap angle is fixed1422 private boolean absoluteFix; // snap angle is absolute1423 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 segment1431 private EastNorth segmentPoint2; // remembered second point of base segment1432 private EastNorth projectionSource; // point that we are projecting to the line1433 1434 private double[] snapAngles;1435 1436 private double pe, pn; // (pe, pn) - direction of snapping line1437 private double e0, n0; // (e0, n0) - origin of snapping line1438 1439 private final String fixFmt = "%d "+tr("FIX");1440 1441 private JCheckBoxMenuItem checkBox;1442 1443 private final MouseListener anglePopupListener = new PopupMenuLauncher(new AnglePopupMenu()) {1444 @Override1445 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 state1456 */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 angles1487 * @param angles The angles1488 */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 graphics1500 * @param mv MapView state1501 * @since 108741502 */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 line1517 } 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 point1551 }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 direction1559 * @param currentEN Current position1560 * @param baseHeading The heading1561 * @param curHeading The current mouse heading1562 */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 angle1582 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 degrees1588 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 line1602 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 point1613 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 position1617 1618 if (baseHeading >= 0) { // there is previous line segment with some heading1619 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 point1657 * @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 projection1690 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 snapping1709 */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 else1785 return delta;1786 }1787 1788 private void unFixOrTurnOff() {1789 if (absoluteFix) {1790 unsetFixedMode();1791 } else {1792 toggleSnapping();1793 }1794 }1795 }1796 1797 1334 private class SnapChangeAction extends JosmAction { 1798 1335 /**
Note:
See TracChangeset
for help on using the changeset viewer.