001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003import static org.openstreetmap.josm.tools.I18n.tr;
004
005import java.awt.Color;
006import java.awt.GridBagConstraints;
007import java.awt.GridBagLayout;
008import java.awt.Insets;
009
010import javax.swing.BorderFactory;
011import javax.swing.JLabel;
012import javax.swing.JPanel;
013import javax.swing.UIManager;
014import javax.swing.event.ChangeEvent;
015import javax.swing.event.ChangeListener;
016
017import org.openstreetmap.gui.jmapviewer.JMapViewer;
018import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
019import org.openstreetmap.josm.data.coor.CoordinateFormat;
020import org.openstreetmap.josm.data.coor.LatLon;
021import org.openstreetmap.josm.data.osm.history.HistoryNode;
022import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
023import org.openstreetmap.josm.gui.NavigatableComponent;
024import org.openstreetmap.josm.gui.util.GuiHelper;
025import org.openstreetmap.josm.gui.widgets.JosmTextArea;
026import org.openstreetmap.josm.tools.CheckParameterUtil;
027import org.openstreetmap.josm.tools.Pair;
028
029/**
030 * An UI widget for displaying differences in the coordinates of two
031 * {@link HistoryNode}s.
032 * @since 2243
033 */
034public class CoordinateInfoViewer extends JPanel {
035
036    /** the model */
037    private transient HistoryBrowserModel model;
038    /** the common info panel for the history node in role REFERENCE_POINT_IN_TIME */
039    private VersionInfoPanel referenceInfoPanel;
040    /** the common info panel for the history node in role CURRENT_POINT_IN_TIME */
041    private VersionInfoPanel currentInfoPanel;
042    /** the info panel for coordinates for the node in role REFERENCE_POINT_IN_TIME */
043    private LatLonViewer referenceLatLonViewer;
044    /** the info panel for coordinates for the node in role CURRENT_POINT_IN_TIME */
045    private LatLonViewer currentLatLonViewer;
046    /** the info panel for distance between the two coordinates */
047    private DistanceViewer distanceViewer;
048    /** the map panel showing the old+new coordinate */
049    private MapViewer mapViewer;
050
051    protected void build() {
052        setLayout(new GridBagLayout());
053        GridBagConstraints gc = new GridBagConstraints();
054
055        // ---------------------------
056        gc.gridx = 0;
057        gc.gridy = 0;
058        gc.gridwidth = 1;
059        gc.gridheight = 1;
060        gc.weightx = 0.5;
061        gc.weighty = 0.0;
062        gc.insets = new Insets(5, 5, 5, 0);
063        gc.fill = GridBagConstraints.HORIZONTAL;
064        gc.anchor = GridBagConstraints.FIRST_LINE_START;
065        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
066        add(referenceInfoPanel, gc);
067
068        gc.gridx = 1;
069        gc.gridy = 0;
070        gc.fill = GridBagConstraints.HORIZONTAL;
071        gc.weightx = 0.5;
072        gc.weighty = 0.0;
073        gc.anchor = GridBagConstraints.FIRST_LINE_START;
074        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
075        add(currentInfoPanel, gc);
076
077        // ---------------------------
078        // the two coordinate panels
079        gc.gridx = 0;
080        gc.gridy = 1;
081        gc.weightx = 0.5;
082        gc.weighty = 0.0;
083        gc.fill = GridBagConstraints.HORIZONTAL;
084        gc.anchor = GridBagConstraints.NORTHWEST;
085        referenceLatLonViewer = new LatLonViewer(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
086        add(referenceLatLonViewer, gc);
087
088        gc.gridx = 1;
089        gc.gridy = 1;
090        gc.weightx = 0.5;
091        gc.weighty = 0.0;
092        gc.fill = GridBagConstraints.HORIZONTAL;
093        gc.anchor = GridBagConstraints.NORTHWEST;
094        currentLatLonViewer = new LatLonViewer(model, PointInTimeType.CURRENT_POINT_IN_TIME);
095        add(currentLatLonViewer, gc);
096
097        // --------------------
098        // the distance panel
099        gc.gridx = 0;
100        gc.gridy = 2;
101        gc.gridwidth = 2;
102        gc.fill = GridBagConstraints.HORIZONTAL;
103        gc.weightx = 1.0;
104        gc.weighty = 0.0;
105        distanceViewer = new DistanceViewer(model);
106        add(distanceViewer, gc);
107
108        // the map panel
109        gc.gridx = 0;
110        gc.gridy = 3;
111        gc.gridwidth = 2;
112        gc.fill = GridBagConstraints.BOTH;
113        gc.weightx = 1.0;
114        gc.weighty = 1.0;
115        mapViewer = new MapViewer(model);
116        add(mapViewer, gc);
117        mapViewer.setZoomContolsVisible(false);
118    }
119
120    /**
121     * Constructs a new {@code CoordinateInfoViewer}.
122     * @param model the model. Must not be null.
123     * @throws IllegalArgumentException if model is null
124     */
125    public CoordinateInfoViewer(HistoryBrowserModel model) {
126        CheckParameterUtil.ensureParameterNotNull(model, "model");
127        setModel(model);
128        build();
129        registerAsChangeListener(model);
130    }
131
132    protected void unregisterAsChangeListener(HistoryBrowserModel model) {
133        if (currentInfoPanel != null) {
134            model.removeChangeListener(currentInfoPanel);
135        }
136        if (referenceInfoPanel != null) {
137            model.removeChangeListener(referenceInfoPanel);
138        }
139        if (currentLatLonViewer != null) {
140            model.removeChangeListener(currentLatLonViewer);
141        }
142        if (referenceLatLonViewer != null) {
143            model.removeChangeListener(referenceLatLonViewer);
144        }
145        if (distanceViewer != null) {
146            model.removeChangeListener(distanceViewer);
147        }
148        if (mapViewer != null) {
149            model.removeChangeListener(mapViewer);
150        }
151    }
152
153    protected void registerAsChangeListener(HistoryBrowserModel model) {
154        if (currentInfoPanel != null) {
155            model.addChangeListener(currentInfoPanel);
156        }
157        if (referenceInfoPanel != null) {
158            model.addChangeListener(referenceInfoPanel);
159        }
160        if (currentLatLonViewer != null) {
161            model.addChangeListener(currentLatLonViewer);
162        }
163        if (referenceLatLonViewer != null) {
164            model.addChangeListener(referenceLatLonViewer);
165        }
166        if (distanceViewer != null) {
167            model.addChangeListener(distanceViewer);
168        }
169        if (mapViewer != null) {
170            model.addChangeListener(mapViewer);
171        }
172    }
173
174    /**
175     * Sets the model for this viewer
176     *
177     * @param model the model.
178     */
179    public void setModel(HistoryBrowserModel model) {
180        if (this.model != null) {
181            unregisterAsChangeListener(model);
182        }
183        this.model = model;
184        if (this.model != null) {
185            registerAsChangeListener(model);
186        }
187    }
188
189    /**
190     * Pans the map to the old+new coordinate
191     * @see JMapViewer#setDisplayToFitMapMarkers()
192     */
193    public void setDisplayToFitMapMarkers() {
194        mapViewer.setDisplayToFitMapMarkers();
195    }
196
197    private static JosmTextArea newTextArea() {
198        JosmTextArea area = new JosmTextArea();
199        GuiHelper.setBackgroundReadable(area, Color.WHITE);
200        area.setEditable(false);
201        area.setOpaque(true);
202        area.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
203        area.setFont(UIManager.getFont("Label.font"));
204        return area;
205    }
206
207    private static class Updater {
208        private final HistoryBrowserModel model;
209        private final PointInTimeType role;
210
211        protected Updater(HistoryBrowserModel model, PointInTimeType role) {
212            this.model = model;
213            this.role = role;
214        }
215
216        protected HistoryOsmPrimitive getPrimitive() {
217            if (model == null || role == null)
218                return null;
219            return model.getPointInTime(role);
220        }
221
222        protected HistoryOsmPrimitive getOppositePrimitive() {
223            if (model == null || role == null)
224                return null;
225            return model.getPointInTime(role.opposite());
226        }
227
228        protected final Pair<LatLon, LatLon> getCoordinates() {
229            HistoryOsmPrimitive p = getPrimitive();
230            HistoryOsmPrimitive opposite = getOppositePrimitive();
231            if (!(p instanceof HistoryNode)) return null;
232            if (!(opposite instanceof HistoryNode)) return null;
233            HistoryNode node = (HistoryNode) p;
234            HistoryNode oppositeNode = (HistoryNode) opposite;
235
236            return Pair.create(node.getCoords(), oppositeNode.getCoords());
237        }
238    }
239
240    /**
241     * A UI widgets which displays the Lan/Lon-coordinates of a {@link HistoryNode}.
242     */
243    private static class LatLonViewer extends JPanel implements ChangeListener {
244
245        private final JosmTextArea lblLat = newTextArea();
246        private final JosmTextArea lblLon = newTextArea();
247        private final transient Updater updater;
248        private final Color modifiedColor;
249
250        protected void build() {
251            setLayout(new GridBagLayout());
252            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
253            GridBagConstraints gc = new GridBagConstraints();
254
255            // --------
256            gc.gridx = 0;
257            gc.gridy = 0;
258            gc.fill = GridBagConstraints.NONE;
259            gc.weightx = 0.0;
260            gc.insets = new Insets(5, 5, 5, 5);
261            gc.anchor = GridBagConstraints.NORTHWEST;
262            add(new JLabel(tr("Latitude: ")), gc);
263
264            // --------
265            gc.gridx = 1;
266            gc.gridy = 0;
267            gc.fill = GridBagConstraints.HORIZONTAL;
268            gc.weightx = 1.0;
269            add(lblLat, gc);
270
271            // --------
272            gc.gridx = 0;
273            gc.gridy = 1;
274            gc.fill = GridBagConstraints.NONE;
275            gc.weightx = 0.0;
276            gc.anchor = GridBagConstraints.NORTHWEST;
277            add(new JLabel(tr("Longitude: ")), gc);
278
279            // --------
280            gc.gridx = 1;
281            gc.gridy = 1;
282            gc.fill = GridBagConstraints.HORIZONTAL;
283            gc.weightx = 1.0;
284            add(lblLon, gc);
285        }
286
287        /**
288         * Constructs a new {@code LatLonViewer}.
289         * @param model a model
290         * @param role the role for this viewer.
291         */
292        LatLonViewer(HistoryBrowserModel model, PointInTimeType role) {
293            this.updater = new Updater(model, role);
294            this.modifiedColor = PointInTimeType.CURRENT_POINT_IN_TIME.equals(role)
295                    ? TwoColumnDiff.Item.DiffItemType.INSERTED.getColor()
296                    : TwoColumnDiff.Item.DiffItemType.DELETED.getColor();
297            build();
298        }
299
300        protected void refresh() {
301            final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
302            if (coordinates == null) return;
303            final LatLon coord = coordinates.a;
304            final LatLon oppositeCoord = coordinates.b;
305
306            // display the coordinates
307            lblLat.setText(coord != null ? coord.latToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)"));
308            lblLon.setText(coord != null ? coord.lonToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)"));
309
310            // update background color to reflect differences in the coordinates
311            if (coord == oppositeCoord ||
312                    (coord != null && oppositeCoord != null && coord.lat() == oppositeCoord.lat())) {
313                GuiHelper.setBackgroundReadable(lblLat, Color.WHITE);
314            } else {
315                GuiHelper.setBackgroundReadable(lblLat, modifiedColor);
316            }
317            if (coord == oppositeCoord ||
318                    (coord != null && oppositeCoord != null && coord.lon() == oppositeCoord.lon())) {
319                GuiHelper.setBackgroundReadable(lblLon, Color.WHITE);
320            } else {
321                GuiHelper.setBackgroundReadable(lblLon, modifiedColor);
322            }
323        }
324
325        @Override
326        public void stateChanged(ChangeEvent e) {
327            refresh();
328        }
329    }
330
331    private static class MapViewer extends JMapViewer implements ChangeListener {
332
333        private final transient Updater updater;
334
335        MapViewer(HistoryBrowserModel model) {
336            this.updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
337            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
338        }
339
340        @Override
341        public void stateChanged(ChangeEvent e) {
342            final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
343            if (coordinates == null) {
344                return;
345            }
346
347            removeAllMapMarkers();
348
349            if (coordinates.a != null) {
350                final MapMarkerDot oldMarker = new MapMarkerDot(coordinates.a.lat(), coordinates.a.lon());
351                oldMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.DELETED.getColor());
352                addMapMarker(oldMarker);
353            }
354            if (coordinates.b != null) {
355                final MapMarkerDot newMarker = new MapMarkerDot(coordinates.b.lat(), coordinates.b.lon());
356                newMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.INSERTED.getColor());
357                addMapMarker(newMarker);
358            }
359
360            setDisplayToFitMapMarkers();
361        }
362    }
363
364    private static class DistanceViewer extends JPanel implements ChangeListener {
365
366        private final JosmTextArea lblDistance = newTextArea();
367        private final transient Updater updater;
368
369        DistanceViewer(HistoryBrowserModel model) {
370            this.updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
371            build();
372        }
373
374        protected void build() {
375            setLayout(new GridBagLayout());
376            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
377            GridBagConstraints gc = new GridBagConstraints();
378
379            // --------
380            gc.gridx = 0;
381            gc.gridy = 0;
382            gc.fill = GridBagConstraints.NONE;
383            gc.weightx = 0.0;
384            gc.insets = new Insets(5, 5, 5, 5);
385            gc.anchor = GridBagConstraints.NORTHWEST;
386            add(new JLabel(tr("Distance: ")), gc);
387
388            // --------
389            gc.gridx = 1;
390            gc.gridy = 0;
391            gc.fill = GridBagConstraints.HORIZONTAL;
392            gc.weightx = 1.0;
393            add(lblDistance, gc);
394        }
395
396        protected void refresh() {
397            final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
398            if (coordinates == null) return;
399            final LatLon coord = coordinates.a;
400            final LatLon oppositeCoord = coordinates.b;
401
402            // update distance
403            //
404            if (coord != null && oppositeCoord != null) {
405                double distance = coord.greatCircleDistance(oppositeCoord);
406                GuiHelper.setBackgroundReadable(lblDistance, distance > 0
407                        ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor()
408                        : Color.WHITE);
409                lblDistance.setText(NavigatableComponent.getDistText(distance));
410            } else {
411                GuiHelper.setBackgroundReadable(lblDistance, coord != oppositeCoord
412                        ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor()
413                        : Color.WHITE);
414                lblDistance.setText(tr("(none)"));
415            }
416        }
417
418        @Override
419        public void stateChanged(ChangeEvent e) {
420            refresh();
421        }
422    }
423}