source: josm/trunk/src/org/openstreetmap/josm/gui/history/CoordinateInfoViewer.java@ 17921

Last change on this file since 17921 was 17684, checked in by simon04, 3 years ago

fix #20678 - History Browser: reset coordinates of a node to previous versions (via popup menu)

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.history;
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.Color;
6import java.awt.GridBagConstraints;
7import java.awt.GridBagLayout;
8import java.awt.Insets;
9import java.awt.event.ActionEvent;
10import java.awt.event.MouseAdapter;
11import java.awt.event.MouseEvent;
12
13import javax.swing.AbstractAction;
14import javax.swing.BorderFactory;
15import javax.swing.JLabel;
16import javax.swing.JPanel;
17import javax.swing.JPopupMenu;
18import javax.swing.UIManager;
19import javax.swing.event.ChangeEvent;
20import javax.swing.event.ChangeListener;
21
22import org.openstreetmap.gui.jmapviewer.JMapViewer;
23import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
24import org.openstreetmap.josm.command.MoveCommand;
25import org.openstreetmap.josm.data.UndoRedoHandler;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.history.HistoryNode;
31import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
32import org.openstreetmap.josm.gui.NavigatableComponent;
33import org.openstreetmap.josm.gui.bbox.JosmMapViewer;
34import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
35import org.openstreetmap.josm.gui.util.GuiHelper;
36import org.openstreetmap.josm.gui.widgets.JosmTextArea;
37import org.openstreetmap.josm.tools.CheckParameterUtil;
38import org.openstreetmap.josm.tools.Destroyable;
39import org.openstreetmap.josm.tools.ImageProvider;
40import org.openstreetmap.josm.tools.Pair;
41
42/**
43 * An UI widget for displaying differences in the coordinates of two
44 * {@link HistoryNode}s.
45 * @since 2243
46 */
47public class CoordinateInfoViewer extends HistoryBrowserPanel {
48
49 /** the info panel for coordinates for the node in role REFERENCE_POINT_IN_TIME */
50 private LatLonViewer referenceLatLonViewer;
51 /** the info panel for coordinates for the node in role CURRENT_POINT_IN_TIME */
52 private LatLonViewer currentLatLonViewer;
53 /** the info panel for distance between the two coordinates */
54 private DistanceViewer distanceViewer;
55 /** the map panel showing the old+new coordinate */
56 private MapViewer mapViewer;
57
58 protected void build() {
59 GridBagConstraints gc = new GridBagConstraints();
60
61 // ---------------------------
62 gc.gridx = 0;
63 gc.gridy = 0;
64 gc.gridwidth = 1;
65 gc.gridheight = 1;
66 gc.weightx = 0.5;
67 gc.weighty = 0.0;
68 gc.insets = new Insets(5, 5, 5, 0);
69 gc.fill = GridBagConstraints.HORIZONTAL;
70 gc.anchor = GridBagConstraints.FIRST_LINE_START;
71 referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
72 add(referenceInfoPanel, gc);
73
74 gc.gridx = 1;
75 gc.gridy = 0;
76 gc.fill = GridBagConstraints.HORIZONTAL;
77 gc.weightx = 0.5;
78 gc.weighty = 0.0;
79 gc.anchor = GridBagConstraints.FIRST_LINE_START;
80 currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
81 add(currentInfoPanel, gc);
82
83 // ---------------------------
84 // the two coordinate panels
85 gc.gridx = 0;
86 gc.gridy = 1;
87 gc.weightx = 0.5;
88 gc.weighty = 0.0;
89 gc.fill = GridBagConstraints.HORIZONTAL;
90 gc.anchor = GridBagConstraints.NORTHWEST;
91 referenceLatLonViewer = new LatLonViewer(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
92 add(referenceLatLonViewer, gc);
93
94 gc.gridx = 1;
95 gc.gridy = 1;
96 gc.weightx = 0.5;
97 gc.weighty = 0.0;
98 gc.fill = GridBagConstraints.HORIZONTAL;
99 gc.anchor = GridBagConstraints.NORTHWEST;
100 currentLatLonViewer = new LatLonViewer(model, PointInTimeType.CURRENT_POINT_IN_TIME);
101 add(currentLatLonViewer, gc);
102
103 // --------------------
104 // the distance panel
105 gc.gridx = 0;
106 gc.gridy = 2;
107 gc.gridwidth = 2;
108 gc.fill = GridBagConstraints.HORIZONTAL;
109 gc.weightx = 1.0;
110 gc.weighty = 0.0;
111 distanceViewer = new DistanceViewer(model);
112 add(distanceViewer, gc);
113
114 // the map panel
115 gc.gridx = 0;
116 gc.gridy = 3;
117 gc.gridwidth = 2;
118 gc.fill = GridBagConstraints.BOTH;
119 gc.weightx = 1.0;
120 gc.weighty = 1.0;
121 mapViewer = new MapViewer(model);
122 add(mapViewer, gc);
123 mapViewer.setZoomControlsVisible(false);
124
125 JPopupMenu popupMenu = new JPopupMenu();
126 popupMenu.add(new RestoreCoordinateAction());
127 setComponentPopupMenu(popupMenu);
128 mapViewer.setComponentPopupMenu(popupMenu);
129 }
130
131 /**
132 * Constructs a new {@code CoordinateInfoViewer}.
133 * @param model the model. Must not be null.
134 * @throws IllegalArgumentException if model is null
135 */
136 public CoordinateInfoViewer(HistoryBrowserModel model) {
137 CheckParameterUtil.ensureParameterNotNull(model, "model");
138 setModel(model);
139 build();
140 registerAsChangeListener(model);
141 }
142
143 @Override
144 protected void unregisterAsChangeListener(HistoryBrowserModel model) {
145 super.unregisterAsChangeListener(model);
146 if (currentLatLonViewer != null) {
147 model.removeChangeListener(currentLatLonViewer);
148 }
149 if (referenceLatLonViewer != null) {
150 model.removeChangeListener(referenceLatLonViewer);
151 }
152 if (distanceViewer != null) {
153 model.removeChangeListener(distanceViewer);
154 }
155 if (mapViewer != null) {
156 model.removeChangeListener(mapViewer);
157 }
158 }
159
160 @Override
161 protected void registerAsChangeListener(HistoryBrowserModel model) {
162 super.registerAsChangeListener(model);
163 if (currentLatLonViewer != null) {
164 model.addChangeListener(currentLatLonViewer);
165 }
166 if (referenceLatLonViewer != null) {
167 model.addChangeListener(referenceLatLonViewer);
168 }
169 if (distanceViewer != null) {
170 model.addChangeListener(distanceViewer);
171 }
172 if (mapViewer != null) {
173 model.addChangeListener(mapViewer);
174 }
175 }
176
177 @Override
178 public void destroy() {
179 super.destroy();
180 referenceLatLonViewer.destroy();
181 currentLatLonViewer.destroy();
182 distanceViewer.destroy();
183 }
184
185 /**
186 * Pans the map to the old+new coordinate
187 * @see JMapViewer#setDisplayToFitMapMarkers()
188 */
189 public void setDisplayToFitMapMarkers() {
190 mapViewer.setDisplayToFitMapMarkers();
191 }
192
193 private static JosmTextArea newTextArea() {
194 JosmTextArea area = new JosmTextArea();
195 GuiHelper.setBackgroundReadable(area, Color.WHITE);
196 area.setEditable(false);
197 area.setOpaque(true);
198 area.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
199 area.setFont(UIManager.getFont("Label.font"));
200 return area;
201 }
202
203 private static class Updater {
204 private final HistoryBrowserModel model;
205 private final PointInTimeType role;
206
207 protected Updater(HistoryBrowserModel model, PointInTimeType role) {
208 this.model = model;
209 this.role = role;
210 }
211
212 protected HistoryOsmPrimitive getPrimitive() {
213 if (model == null || role == null)
214 return null;
215 return model.getPointInTime(role);
216 }
217
218 protected HistoryOsmPrimitive getOppositePrimitive() {
219 if (model == null || role == null)
220 return null;
221 return model.getPointInTime(role.opposite());
222 }
223
224 protected final Pair<LatLon, LatLon> getCoordinates() {
225 HistoryOsmPrimitive p = getPrimitive();
226 if (!(p instanceof HistoryNode)) return null;
227 HistoryOsmPrimitive opposite = getOppositePrimitive();
228 if (!(opposite instanceof HistoryNode)) return null;
229 HistoryNode node = (HistoryNode) p;
230 HistoryNode oppositeNode = (HistoryNode) opposite;
231
232 return Pair.create(node.getCoords(), oppositeNode.getCoords());
233 }
234 }
235
236 /**
237 * A UI widgets which displays the Lan/Lon-coordinates of a {@link HistoryNode}.
238 */
239 private static class LatLonViewer extends JPanel implements ChangeListener, Destroyable {
240
241 private final JosmTextArea lblLat = newTextArea();
242 private final JosmTextArea lblLon = newTextArea();
243 private final transient Updater updater;
244 private final Color modifiedColor;
245
246 protected void build() {
247 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
248 GridBagConstraints gc = new GridBagConstraints();
249
250 // --------
251 gc.gridx = 0;
252 gc.gridy = 0;
253 gc.fill = GridBagConstraints.NONE;
254 gc.weightx = 0.0;
255 gc.insets = new Insets(5, 5, 5, 5);
256 gc.anchor = GridBagConstraints.NORTHWEST;
257 add(new JLabel(tr("Latitude: ")), gc);
258
259 // --------
260 gc.gridx = 1;
261 gc.gridy = 0;
262 gc.fill = GridBagConstraints.HORIZONTAL;
263 gc.weightx = 1.0;
264 add(lblLat, gc);
265
266 // --------
267 gc.gridx = 0;
268 gc.gridy = 1;
269 gc.fill = GridBagConstraints.NONE;
270 gc.weightx = 0.0;
271 gc.anchor = GridBagConstraints.NORTHWEST;
272 add(new JLabel(tr("Longitude: ")), gc);
273
274 // --------
275 gc.gridx = 1;
276 gc.gridy = 1;
277 gc.fill = GridBagConstraints.HORIZONTAL;
278 gc.weightx = 1.0;
279 add(lblLon, gc);
280 }
281
282 /**
283 * Constructs a new {@code LatLonViewer}.
284 * @param model a model
285 * @param role the role for this viewer.
286 */
287 LatLonViewer(HistoryBrowserModel model, PointInTimeType role) {
288 super(new GridBagLayout());
289 this.updater = new Updater(model, role);
290 this.modifiedColor = PointInTimeType.CURRENT_POINT_IN_TIME == role
291 ? TwoColumnDiff.Item.DiffItemType.INSERTED.getColor()
292 : TwoColumnDiff.Item.DiffItemType.DELETED.getColor();
293 build();
294 }
295
296 protected void refresh() {
297 final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
298 if (coordinates == null) return;
299 final LatLon coord = coordinates.a;
300 final LatLon oppositeCoord = coordinates.b;
301
302 // display the coordinates
303 lblLat.setText(coord != null ? DecimalDegreesCoordinateFormat.INSTANCE.latToString(coord) : tr("(none)"));
304 lblLon.setText(coord != null ? DecimalDegreesCoordinateFormat.INSTANCE.lonToString(coord) : tr("(none)"));
305
306 // update background color to reflect differences in the coordinates
307 if (coord == oppositeCoord ||
308 (coord != null && oppositeCoord != null && coord.lat() == oppositeCoord.lat())) {
309 GuiHelper.setBackgroundReadable(lblLat, Color.WHITE);
310 } else {
311 GuiHelper.setBackgroundReadable(lblLat, modifiedColor);
312 }
313 if (coord == oppositeCoord ||
314 (coord != null && oppositeCoord != null && coord.lon() == oppositeCoord.lon())) {
315 GuiHelper.setBackgroundReadable(lblLon, Color.WHITE);
316 } else {
317 GuiHelper.setBackgroundReadable(lblLon, modifiedColor);
318 }
319 }
320
321 @Override
322 public void stateChanged(ChangeEvent e) {
323 refresh();
324 }
325
326 @Override
327 public void destroy() {
328 lblLat.destroy();
329 lblLon.destroy();
330 }
331 }
332
333 private static class MapViewer extends JosmMapViewer implements ChangeListener {
334
335 private final transient Updater updater;
336
337 MapViewer(HistoryBrowserModel model) {
338 this.updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
339 setTileSource(SlippyMapBBoxChooser.DefaultOsmTileSourceProvider.get()); // for attribution
340 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
341 addMouseListener(new MouseAdapter() {
342 @Override
343 public void mouseClicked(MouseEvent e) {
344 if (e.getButton() == MouseEvent.BUTTON1) {
345 getAttribution().handleAttribution(e.getPoint(), true);
346 }
347 }
348 });
349 }
350
351 @Override
352 public void stateChanged(ChangeEvent e) {
353 final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
354 if (coordinates == null) {
355 return;
356 }
357
358 removeAllMapMarkers();
359
360 if (coordinates.a != null) {
361 final MapMarkerDot oldMarker = new MapMarkerDot(coordinates.a.lat(), coordinates.a.lon());
362 oldMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.DELETED.getColor());
363 addMapMarker(oldMarker);
364 }
365 if (coordinates.b != null) {
366 final MapMarkerDot newMarker = new MapMarkerDot(coordinates.b.lat(), coordinates.b.lon());
367 newMarker.setBackColor(TwoColumnDiff.Item.DiffItemType.INSERTED.getColor());
368 addMapMarker(newMarker);
369 }
370
371 super.setDisplayToFitMapMarkers();
372 }
373 }
374
375 private static class DistanceViewer extends JPanel implements ChangeListener, Destroyable {
376
377 private final JosmTextArea lblDistance = newTextArea();
378 private final transient Updater updater;
379
380 DistanceViewer(HistoryBrowserModel model) {
381 super(new GridBagLayout());
382 updater = new Updater(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
383 build();
384 }
385
386 protected void build() {
387 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
388 GridBagConstraints gc = new GridBagConstraints();
389
390 // --------
391 gc.gridx = 0;
392 gc.gridy = 0;
393 gc.fill = GridBagConstraints.NONE;
394 gc.weightx = 0.0;
395 gc.insets = new Insets(5, 5, 5, 5);
396 gc.anchor = GridBagConstraints.NORTHWEST;
397 add(new JLabel(tr("Distance: ")), gc);
398
399 // --------
400 gc.gridx = 1;
401 gc.gridy = 0;
402 gc.fill = GridBagConstraints.HORIZONTAL;
403 gc.weightx = 1.0;
404 add(lblDistance, gc);
405 }
406
407 protected void refresh() {
408 final Pair<LatLon, LatLon> coordinates = updater.getCoordinates();
409 if (coordinates == null) return;
410 final LatLon coord = coordinates.a;
411 final LatLon oppositeCoord = coordinates.b;
412
413 // update distance
414 //
415 if (coord != null && oppositeCoord != null) {
416 double distance = coord.greatCircleDistance(oppositeCoord);
417 GuiHelper.setBackgroundReadable(lblDistance, distance > 0
418 ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor()
419 : Color.WHITE);
420 lblDistance.setText(NavigatableComponent.getDistText(distance));
421 } else {
422 GuiHelper.setBackgroundReadable(lblDistance, coord != oppositeCoord
423 ? TwoColumnDiff.Item.DiffItemType.CHANGED.getColor()
424 : Color.WHITE);
425 lblDistance.setText(tr("(none)"));
426 }
427 }
428
429 @Override
430 public void stateChanged(ChangeEvent e) {
431 refresh();
432 }
433
434 @Override
435 public void destroy() {
436 lblDistance.destroy();
437 }
438 }
439
440 private class RestoreCoordinateAction extends AbstractAction {
441
442 RestoreCoordinateAction() {
443 super(tr("Restore"));
444 new ImageProvider("undo").getResource().attachImageIcon(this, true);
445 }
446
447 @Override
448 public void actionPerformed(ActionEvent e) {
449 OsmPrimitive primitive = getPrimitiveFromDataSet(PointInTimeType.REFERENCE_POINT_IN_TIME);
450 if (!(primitive instanceof Node)) {
451 return;
452 }
453 HistoryOsmPrimitive historyPrimitive = model.getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME);
454 if (!(historyPrimitive instanceof HistoryNode) || ((HistoryNode) historyPrimitive).getCoords() == null) {
455 return;
456 }
457 MoveCommand command = new MoveCommand(((Node) primitive), ((HistoryNode) historyPrimitive).getCoords());
458 UndoRedoHandler.getInstance().add(command);
459 }
460 }
461}
Note: See TracBrowser for help on using the repository browser.