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