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