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