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}