// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.history; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openstreetmap.gui.jmapviewer.JMapViewer; import org.openstreetmap.gui.jmapviewer.MapMarkerDot; import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource; import org.openstreetmap.josm.data.coor.CoordinateFormat; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.history.HistoryNode; import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; import org.openstreetmap.josm.gui.NavigatableComponent; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.JosmTextArea; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Pair; /** * An UI widget for displaying differences in the coordinates of two * {@link HistoryNode}s. * @since 2243 */ public class CoordinateInfoViewer extends JPanel { /** the model */ private transient HistoryBrowserModel model; /** the common info panel for the history node in role REFERENCE_POINT_IN_TIME */ private VersionInfoPanel referenceInfoPanel; /** the common info panel for the history node in role CURRENT_POINT_IN_TIME */ private VersionInfoPanel currentInfoPanel; /** the info panel for coordinates for the node in role REFERENCE_POINT_IN_TIME */ private LatLonViewer referenceLatLonViewer; /** the info panel for coordinates for the node in role CURRENT_POINT_IN_TIME */ private LatLonViewer currentLatLonViewer; /** the info panel for distance between the two coordinates */ private DistanceViewer distanceViewer; /** the map panel showing the old+new coordinate */ private MapViewer mapViewer; protected void build() { setLayout(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); // --------------------------- gc.gridx = 0; gc.gridy = 0; gc.gridwidth = 1; gc.gridheight = 1; gc.weightx = 0.5; gc.weighty = 0.0; gc.insets = new Insets(5, 5, 5, 0); gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.FIRST_LINE_START; referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME); add(referenceInfoPanel, gc); gc.gridx = 1; gc.gridy = 0; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 0.5; gc.weighty = 0.0; gc.anchor = GridBagConstraints.FIRST_LINE_START; currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME); add(currentInfoPanel, gc); // --------------------------- // the two coordinate panels gc.gridx = 0; gc.gridy = 1; gc.weightx = 0.5; gc.weighty = 0.0; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.NORTHWEST; referenceLatLonViewer = new LatLonViewer(model, PointInTimeType.REFERENCE_POINT_IN_TIME); add(referenceLatLonViewer, gc); gc.gridx = 1; gc.gridy = 1; gc.weightx = 0.5; gc.weighty = 0.0; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.NORTHWEST; currentLatLonViewer = new LatLonViewer(model, PointInTimeType.CURRENT_POINT_IN_TIME); add(currentLatLonViewer, gc); // -------------------- // the distance panel gc.gridx = 0; gc.gridy = 2; gc.gridwidth = 2; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; gc.weighty = 0.0; distanceViewer = new DistanceViewer(model); add(distanceViewer, gc); // the map panel gc.gridx = 0; gc.gridy = 3; gc.gridwidth = 2; gc.fill = GridBagConstraints.BOTH; gc.weightx = 1.0; gc.weighty = 1.0; mapViewer = new MapViewer(model); add(mapViewer, gc); mapViewer.setZoomContolsVisible(false); } /** * Constructs a new {@code CoordinateInfoViewer}. * @param model the model. Must not be null. * @throws IllegalArgumentException if model is null */ public CoordinateInfoViewer(HistoryBrowserModel model) { CheckParameterUtil.ensureParameterNotNull(model, "model"); setModel(model); build(); registerAsChangeListener(model); } protected void unregisterAsChangeListener(HistoryBrowserModel model) { if (currentInfoPanel != null) { model.removeChangeListener(currentInfoPanel); } if (referenceInfoPanel != null) { model.removeChangeListener(referenceInfoPanel); } if (currentLatLonViewer != null) { model.removeChangeListener(currentLatLonViewer); } if (referenceLatLonViewer != null) { model.removeChangeListener(referenceLatLonViewer); } if (distanceViewer != null) { model.removeChangeListener(distanceViewer); } if (mapViewer != null) { model.removeChangeListener(mapViewer); } } protected void registerAsChangeListener(HistoryBrowserModel model) { if (currentInfoPanel != null) { model.addChangeListener(currentInfoPanel); } if (referenceInfoPanel != null) { model.addChangeListener(referenceInfoPanel); } if (currentLatLonViewer != null) { model.addChangeListener(currentLatLonViewer); } if (referenceLatLonViewer != null) { model.addChangeListener(referenceLatLonViewer); } if (distanceViewer != null) { model.addChangeListener(distanceViewer); } if (mapViewer != null) { model.addChangeListener(mapViewer); } } /** * Sets the model for this viewer * * @param model the model. */ public void setModel(HistoryBrowserModel model) { if (this.model != null) { unregisterAsChangeListener(model); } this.model = model; if (this.model != null) { registerAsChangeListener(model); } } /** * Pans the map to the old+new coordinate * @see JMapViewer#setDisplayToFitMapMarkers() */ public void setDisplayToFitMapMarkers() { mapViewer.setDisplayToFitMapMarkers(); } private static JosmTextArea newTextArea() { JosmTextArea area = new JosmTextArea(); GuiHelper.setBackgroundReadable(area, Color.WHITE); area.setEditable(false); area.setOpaque(true); area.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); area.setFont(UIManager.getFont("Label.font")); return area; } private static class Updater { private final HistoryBrowserModel model; private final PointInTimeType role; protected Updater(HistoryBrowserModel model, PointInTimeType role) { this.model = model; this.role = role; } protected HistoryOsmPrimitive getPrimitive() { if (model == null || role == null) return null; return model.getPointInTime(role); } protected HistoryOsmPrimitive getOppositePrimitive() { if (model == null || role == null) return null; return model.getPointInTime(role.opposite()); } protected final Pair<LatLon, LatLon> getCoordinates() { HistoryOsmPrimitive p = getPrimitive(); if (!(p instanceof HistoryNode)) return null; HistoryOsmPrimitive opposite = getOppositePrimitive(); if (!(opposite instanceof HistoryNode)) return null; HistoryNode node = (HistoryNode) p; HistoryNode oppositeNode = (HistoryNode) opposite; return Pair.create(node.getCoords(), oppositeNode.getCoords()); } } /** * A UI widgets which displays the Lan/Lon-coordinates of a {@link HistoryNode}. */ private static class LatLonViewer extends JPanel implements ChangeListener { private final JosmTextArea lblLat = newTextArea(); private final JosmTextArea lblLon = newTextArea(); private final transient Updater updater; private final Color modifiedColor; protected void build() { setLayout(new GridBagLayout()); setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY)); GridBagConstraints gc = new GridBagConstraints(); // -------- gc.gridx = 0; gc.gridy = 0; gc.fill = GridBagConstraints.NONE; gc.weightx = 0.0; gc.insets = new Insets(5, 5, 5, 5); gc.anchor = GridBagConstraints.NORTHWEST; add(new JLabel(tr("Latitude: ")), gc); // -------- gc.gridx = 1; gc.gridy = 0; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; add(lblLat, gc); // -------- gc.gridx = 0; gc.gridy = 1; gc.fill = GridBagConstraints.NONE; gc.weightx = 0.0; gc.anchor = GridBagConstraints.NORTHWEST; add(new JLabel(tr("Longitude: ")), gc); // -------- gc.gridx = 1; gc.gridy = 1; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; add(lblLon, gc); } /** * Constructs a new {@code LatLonViewer}. * @param model a model * @param role the role for this viewer. */ LatLonViewer(HistoryBrowserModel model, PointInTimeType role) { this.updater = new Updater(model, role); this.modifiedColor = PointInTimeType.CURRENT_POINT_IN_TIME.equals(role) ? TwoColumnDiff.Item.DiffItemType.INSERTED.getColor() : TwoColumnDiff.Item.DiffItemType.DELETED.getColor(); build(); } protected void refresh() { final Pair<LatLon, LatLon> coordinates = updater.getCoordinates(); if (coordinates == null) return; final LatLon coord = coordinates.a; final LatLon oppositeCoord = coordinates.b; // display the coordinates lblLat.setText(coord != null ? coord.latToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)")); lblLon.setText(coord != null ? coord.lonToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)")); // update background color to reflect differences in the coordinates if (coord == oppositeCoord || (coord != null && oppositeCoord != null && coord.lat() == oppositeCoord.lat())) { GuiHelper.setBackgroundReadable(lblLat, Color.WHITE); } else { GuiHelper.setBackgroundReadable(lblLat, modifiedColor); } if (coord == oppositeCoord || (coord != null && oppositeCoord != null && coord.lon() == oppositeCoord.lon())) { GuiHelper.setBackgroundReadable(lblLon, Color.WHITE); } else { GuiHelper.setBackgroundReadable(lblLon, modifiedColor); } } @Override public void stateChanged(ChangeEvent e) { refresh(); } } private static class MapViewer extends JMapViewer implements ChangeListener { private final transient Updater updater; MapViewer(HistoryBrowserModel model) { this.updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME); setTileSource(new OsmTileSource.Mapnik()); // for attribution setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY)); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { getAttribution().handleAttribution(e.getPoint(), true); } } }); } @Override public void stateChanged(ChangeEvent e) { final Pair<LatLon, LatLon> coordinates = updater.getCoordinates(); if (coordinates == null) { return; } removeAllMapMarkers(); if (coordinates.a != null) { final MapMarkerDot oldMarker = new MapMarkerDot(coordinates.a.lat(), coordinates.a.lon()); oldMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.DELETED.getColor()); addMapMarker(oldMarker); } if (coordinates.b != null) { final MapMarkerDot newMarker = new MapMarkerDot(coordinates.b.lat(), coordinates.b.lon()); newMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.INSERTED.getColor()); addMapMarker(newMarker); } setDisplayToFitMapMarkers(); } } private static class DistanceViewer extends JPanel implements ChangeListener { private final JosmTextArea lblDistance = newTextArea(); private final transient Updater updater; DistanceViewer(HistoryBrowserModel model) { this.updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME); build(); } protected void build() { setLayout(new GridBagLayout()); setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY)); GridBagConstraints gc = new GridBagConstraints(); // -------- gc.gridx = 0; gc.gridy = 0; gc.fill = GridBagConstraints.NONE; gc.weightx = 0.0; gc.insets = new Insets(5, 5, 5, 5); gc.anchor = GridBagConstraints.NORTHWEST; add(new JLabel(tr("Distance: ")), gc); // -------- gc.gridx = 1; gc.gridy = 0; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; add(lblDistance, gc); } protected void refresh() { final Pair<LatLon, LatLon> coordinates = updater.getCoordinates(); if (coordinates == null) return; final LatLon coord = coordinates.a; final LatLon oppositeCoord = coordinates.b; // update distance // if (coord != null && oppositeCoord != null) { double distance = coord.greatCircleDistance(oppositeCoord); GuiHelper.setBackgroundReadable(lblDistance, distance > 0 ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor() : Color.WHITE); lblDistance.setText(NavigatableComponent.getDistText(distance)); } else { GuiHelper.setBackgroundReadable(lblDistance, coord != oppositeCoord ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor() : Color.WHITE); lblDistance.setText(tr("(none)")); } } @Override public void stateChanged(ChangeEvent e) { refresh(); } } }