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