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.josm.data.coor.CoordinateFormat;
017import org.openstreetmap.josm.data.coor.LatLon;
018import org.openstreetmap.josm.data.osm.history.HistoryNode;
019import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
020import org.openstreetmap.josm.gui.NavigatableComponent;
021import org.openstreetmap.josm.tools.CheckParameterUtil;
022
023/**
024 * An UI widget for displaying differences in the coordinates of two
025 * {@link HistoryNode}s.
026 *
027 */
028public class CoordinateInfoViewer extends JPanel {
029
030    /** background color used when the coordinates are different */
031    public static final Color BGCOLOR_DIFFERENCE = new Color(255, 197, 197);
032
033    /** the model */
034    private transient HistoryBrowserModel model;
035    /** the common info panel for the history node in role REFERENCE_POINT_IN_TIME */
036    private VersionInfoPanel referenceInfoPanel;
037    /** the common info panel for the history node in role CURRENT_POINT_IN_TIME */
038    private VersionInfoPanel currentInfoPanel;
039    /** the info panel for coordinates for the node in role REFERENCE_POINT_IN_TIME */
040    private LatLonViewer referenceLatLonViewer;
041    /** the info panel for coordinates for the node in role CURRENT_POINT_IN_TIME */
042    private LatLonViewer currentLatLonViewer;
043    /** the info panel for distance between the two coordinates */
044    private DistanceViewer distanceViewer;
045
046    protected void build() {
047        setLayout(new GridBagLayout());
048        GridBagConstraints gc = new GridBagConstraints();
049
050        // ---------------------------
051        gc.gridx = 0;
052        gc.gridy = 0;
053        gc.gridwidth = 1;
054        gc.gridheight = 1;
055        gc.weightx = 0.5;
056        gc.weighty = 0.0;
057        gc.insets = new Insets(5, 5, 5, 0);
058        gc.fill = GridBagConstraints.HORIZONTAL;
059        gc.anchor = GridBagConstraints.FIRST_LINE_START;
060        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
061        add(referenceInfoPanel, gc);
062
063        gc.gridx = 1;
064        gc.gridy = 0;
065        gc.fill = GridBagConstraints.HORIZONTAL;
066        gc.weightx = 0.5;
067        gc.weighty = 0.0;
068        gc.anchor = GridBagConstraints.FIRST_LINE_START;
069        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
070        add(currentInfoPanel, gc);
071
072        // ---------------------------
073        // the two coordinate panels
074        gc.gridx = 0;
075        gc.gridy = 1;
076        gc.weightx = 0.5;
077        gc.weighty = 1.0;
078        gc.fill = GridBagConstraints.BOTH;
079        gc.anchor = GridBagConstraints.NORTHWEST;
080        add(referenceLatLonViewer = new LatLonViewer(model, PointInTimeType.REFERENCE_POINT_IN_TIME), gc);
081
082        gc.gridx = 1;
083        gc.gridy = 1;
084        gc.weightx = 0.5;
085        gc.weighty = 1.0;
086        gc.fill = GridBagConstraints.BOTH;
087        gc.anchor = GridBagConstraints.NORTHWEST;
088        add(currentLatLonViewer = new LatLonViewer(model, PointInTimeType.CURRENT_POINT_IN_TIME), gc);
089
090        // --------------------
091        // the distance panel
092        gc.gridx = 0;
093        gc.gridy = 2;
094        gc.gridwidth = 2;
095        gc.fill = GridBagConstraints.HORIZONTAL;
096        gc.weightx = 1.0;
097        gc.weighty = 0.0;
098        add(distanceViewer = new DistanceViewer(model), gc);
099    }
100
101    /**
102     * Constructs a new {@code CoordinateInfoViewer}.
103     * @param model the model. Must not be null.
104     * @throws IllegalArgumentException if model is null
105     */
106    public CoordinateInfoViewer(HistoryBrowserModel model) {
107        CheckParameterUtil.ensureParameterNotNull(model, "model");
108        setModel(model);
109        build();
110        registerAsObserver(model);
111    }
112
113    protected void unregisterAsObserver(HistoryBrowserModel model) {
114        if (currentInfoPanel != null) {
115            model.deleteObserver(currentInfoPanel);
116        }
117        if (referenceInfoPanel != null) {
118            model.deleteObserver(referenceInfoPanel);
119        }
120        if (currentLatLonViewer != null) {
121            model.deleteObserver(currentLatLonViewer);
122        }
123        if (referenceLatLonViewer != null) {
124            model.deleteObserver(referenceLatLonViewer);
125        }
126        if (distanceViewer != null) {
127            model.deleteObserver(distanceViewer);
128        }
129    }
130
131    protected void registerAsObserver(HistoryBrowserModel model) {
132        if (currentInfoPanel != null) {
133            model.addObserver(currentInfoPanel);
134        }
135        if (referenceInfoPanel != null) {
136            model.addObserver(referenceInfoPanel);
137        }
138        if (currentLatLonViewer != null) {
139            model.addObserver(currentLatLonViewer);
140        }
141        if (referenceLatLonViewer != null) {
142            model.addObserver(referenceLatLonViewer);
143        }
144        if (distanceViewer != null) {
145            model.addObserver(distanceViewer);
146        }
147    }
148
149    /**
150     * Sets the model for this viewer
151     *
152     * @param model the model.
153     */
154    public void setModel(HistoryBrowserModel model) {
155        if (this.model != null) {
156            unregisterAsObserver(model);
157        }
158        this.model = model;
159        if (this.model != null) {
160            registerAsObserver(model);
161        }
162    }
163
164    /**
165     * A UI widgets which displays the Lan/Lon-coordinates of a
166     * {@link HistoryNode}.
167     *
168     */
169    private static class LatLonViewer extends JPanel implements Observer {
170
171        private JLabel lblLat;
172        private JLabel lblLon;
173        private transient HistoryBrowserModel model;
174        private PointInTimeType role;
175
176        protected LatLon coord;
177        protected LatLon oppositeCoord;
178
179        protected HistoryOsmPrimitive getPrimitive() {
180            if (model == null || role == null)
181                return null;
182            return model.getPointInTime(role);
183        }
184
185        protected HistoryOsmPrimitive getOppositePrimitive() {
186            if (model == null || role == null)
187                return null;
188            return model.getPointInTime(role.opposite());
189        }
190
191        protected void build() {
192            setLayout(new GridBagLayout());
193            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
194            GridBagConstraints gc = new GridBagConstraints();
195
196            // --------
197            gc.gridx = 0;
198            gc.gridy = 0;
199            gc.fill = GridBagConstraints.NONE;
200            gc.weightx = 0.0;
201            gc.insets = new Insets(5, 5, 5, 5);
202            gc.anchor = GridBagConstraints.NORTHWEST;
203            add(new JLabel(tr("Latitude: ")), gc);
204
205            // --------
206            gc.gridx = 1;
207            gc.gridy = 0;
208            gc.fill = GridBagConstraints.HORIZONTAL;
209            gc.weightx = 1.0;
210            add(lblLat = new JLabel(), gc);
211            lblLat.setBackground(Color.WHITE);
212            lblLat.setOpaque(true);
213            lblLat.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
214
215            // --------
216            gc.gridx = 0;
217            gc.gridy = 1;
218            gc.fill = GridBagConstraints.NONE;
219            gc.weightx = 0.0;
220            gc.anchor = GridBagConstraints.NORTHWEST;
221            add(new JLabel(tr("Longitude: ")), gc);
222
223            // --------
224            gc.gridx = 1;
225            gc.gridy = 1;
226            gc.fill = GridBagConstraints.HORIZONTAL;
227            gc.weightx = 1.0;
228            add(lblLon = new JLabel(), gc);
229            lblLon.setBackground(Color.WHITE);
230            lblLon.setOpaque(true);
231            lblLon.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
232
233            // fill the remaining space
234            gc.gridx = 0;
235            gc.gridy = 2;
236            gc.gridwidth = 2;
237            gc.fill = GridBagConstraints.BOTH;
238            gc.weightx = 1.0;
239            gc.weighty = 1.0;
240            add(new JPanel(), gc);
241        }
242
243        /**
244         *
245         * @param model a model
246         * @param role the role for this viewer.
247         */
248        LatLonViewer(HistoryBrowserModel model, PointInTimeType role) {
249            build();
250            this.model = model;
251            this.role = role;
252        }
253
254        protected final boolean prepareRefresh() {
255            HistoryOsmPrimitive p = getPrimitive();
256            HistoryOsmPrimitive  opposite = getOppositePrimitive();
257            if (!(p instanceof HistoryNode)) return false;
258            if (!(opposite instanceof HistoryNode)) return false;
259            HistoryNode node = (HistoryNode) p;
260            HistoryNode oppositeNode = (HistoryNode) opposite;
261
262            coord = node.getCoords();
263            oppositeCoord = oppositeNode.getCoords();
264            return true;
265        }
266
267        protected void refresh() {
268            if (!prepareRefresh()) return;
269
270            // display the coordinates
271            lblLat.setText(coord != null ? coord.latToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)"));
272            lblLon.setText(coord != null ? coord.lonToString(CoordinateFormat.DECIMAL_DEGREES) : tr("(none)"));
273
274            // update background color to reflect differences in the coordinates
275            if (coord == oppositeCoord ||
276                    (coord != null && oppositeCoord != null && coord.lat() == oppositeCoord.lat())) {
277                lblLat.setBackground(Color.WHITE);
278            } else {
279                lblLat.setBackground(BGCOLOR_DIFFERENCE);
280            }
281            if (coord == oppositeCoord ||
282                    (coord != null && oppositeCoord != null && coord.lon() == oppositeCoord.lon())) {
283                lblLon.setBackground(Color.WHITE);
284            } else {
285                lblLon.setBackground(BGCOLOR_DIFFERENCE);
286            }
287        }
288
289        @Override
290        public void update(Observable o, Object arg) {
291            refresh();
292        }
293    }
294
295    private static class DistanceViewer extends LatLonViewer {
296
297        private JLabel lblDistance;
298
299        DistanceViewer(HistoryBrowserModel model) {
300            super(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
301        }
302
303        @Override
304        protected void build() {
305            setLayout(new GridBagLayout());
306            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
307            GridBagConstraints gc = new GridBagConstraints();
308
309            // --------
310            gc.gridx = 0;
311            gc.gridy = 0;
312            gc.fill = GridBagConstraints.NONE;
313            gc.weightx = 0.0;
314            gc.insets = new Insets(5, 5, 5, 5);
315            gc.anchor = GridBagConstraints.NORTHWEST;
316            add(new JLabel(tr("Distance: ")), gc);
317
318            // --------
319            gc.gridx = 1;
320            gc.gridy = 0;
321            gc.fill = GridBagConstraints.HORIZONTAL;
322            gc.weightx = 1.0;
323            add(lblDistance = new JLabel(), gc);
324            lblDistance.setBackground(Color.WHITE);
325            lblDistance.setOpaque(true);
326            lblDistance.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
327        }
328
329        @Override
330        protected void refresh() {
331            if (!prepareRefresh()) return;
332
333            // update distance
334            //
335            if (coord != null && oppositeCoord != null) {
336                double distance = coord.greatCircleDistance(oppositeCoord);
337                if (distance > 0) {
338                    lblDistance.setBackground(BGCOLOR_DIFFERENCE);
339                } else {
340                    lblDistance.setBackground(Color.WHITE);
341                }
342                lblDistance.setText(NavigatableComponent.getDistText(distance));
343            } else {
344                lblDistance.setBackground(coord != oppositeCoord ? BGCOLOR_DIFFERENCE : Color.WHITE);
345                lblDistance.setText(tr("(none)"));
346            }
347        }
348    }
349}