001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.history; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.DateFormat; 007import java.util.ArrayList; 008import java.util.Collections; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Observable; 012 013import javax.swing.JTable; 014import javax.swing.table.AbstractTableModel; 015import javax.swing.table.TableModel; 016 017import org.openstreetmap.josm.Main; 018import org.openstreetmap.josm.data.osm.Node; 019import org.openstreetmap.josm.data.osm.OsmPrimitive; 020import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 021import org.openstreetmap.josm.data.osm.Relation; 022import org.openstreetmap.josm.data.osm.RelationMember; 023import org.openstreetmap.josm.data.osm.RelationMemberData; 024import org.openstreetmap.josm.data.osm.User; 025import org.openstreetmap.josm.data.osm.UserInfo; 026import org.openstreetmap.josm.data.osm.Way; 027import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 028import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 029import org.openstreetmap.josm.data.osm.event.DataSetListener; 030import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 031import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 032import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 033import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 034import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 035import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 036import org.openstreetmap.josm.data.osm.history.History; 037import org.openstreetmap.josm.data.osm.history.HistoryNode; 038import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 039import org.openstreetmap.josm.data.osm.history.HistoryRelation; 040import org.openstreetmap.josm.data.osm.history.HistoryWay; 041import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; 042import org.openstreetmap.josm.gui.JosmUserIdentityManager; 043import org.openstreetmap.josm.gui.MapView; 044import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 045import org.openstreetmap.josm.gui.layer.Layer; 046import org.openstreetmap.josm.gui.layer.OsmDataLayer; 047import org.openstreetmap.josm.tools.CheckParameterUtil; 048import org.openstreetmap.josm.tools.date.DateUtils; 049 050/** 051 * This is the model used by the history browser. 052 * 053 * The model state consists of the following elements: 054 * <ul> 055 * <li>the {@link History} of a specific {@link OsmPrimitive}</li> 056 * <li>a dedicated version in this {@link History} called the {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li> 057 * <li>another version in this {@link History} called the {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li> 058 * </ul> 059 * {@link HistoryBrowser} always compares the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} with the 060 * {@link PointInTimeType#CURRENT_POINT_IN_TIME}. 061 062 * This model provides various {@link TableModel}s for {@link JTable}s used in {@link HistoryBrowser}, for 063 * instance: 064 * <ul> 065 * <li>{@link #getTagTableModel(PointInTimeType)} replies a {@link TableModel} for the tags of either of 066 * the two selected versions</li> 067 * <li>{@link #getNodeListTableModel(PointInTimeType)} replies a {@link TableModel} for the list of nodes of 068 * the two selected versions (if the current history provides information about a {@link Way}</li> 069 * <li> {@link #getRelationMemberTableModel(PointInTimeType)} replies a {@link TableModel} for the list of relation 070 * members of the two selected versions (if the current history provides information about a {@link Relation}</li> 071 * </ul> 072 * 073 * @see HistoryBrowser 074 */ 075public class HistoryBrowserModel extends Observable implements LayerChangeListener, DataSetListener { 076 /** the history of an OsmPrimitive */ 077 private History history; 078 private HistoryOsmPrimitive reference; 079 private HistoryOsmPrimitive current; 080 /** 081 * latest isn't a reference of history. It's a clone of the currently edited 082 * {@link OsmPrimitive} in the current edit layer. 083 */ 084 private HistoryOsmPrimitive latest; 085 086 private VersionTableModel versionTableModel; 087 private TagTableModel currentTagTableModel; 088 private TagTableModel referenceTagTableModel; 089 private DiffTableModel currentRelationMemberTableModel; 090 private DiffTableModel referenceRelationMemberTableModel; 091 private DiffTableModel referenceNodeListTableModel; 092 private DiffTableModel currentNodeListTableModel; 093 094 /** 095 * constructor 096 */ 097 public HistoryBrowserModel() { 098 versionTableModel = new VersionTableModel(); 099 currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME); 100 referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME); 101 referenceNodeListTableModel = new DiffTableModel(); 102 currentNodeListTableModel = new DiffTableModel(); 103 currentRelationMemberTableModel = new DiffTableModel(); 104 referenceRelationMemberTableModel = new DiffTableModel(); 105 106 OsmDataLayer editLayer = Main.main.getEditLayer(); 107 if (editLayer != null) { 108 editLayer.data.addDataSetListener(this); 109 } 110 MapView.addLayerChangeListener(this); 111 } 112 113 /** 114 * Creates a new history browser model for a given history. 115 * 116 * @param history the history. Must not be null. 117 * @throws IllegalArgumentException thrown if history is null 118 */ 119 public HistoryBrowserModel(History history) { 120 this(); 121 CheckParameterUtil.ensureParameterNotNull(history, "history"); 122 setHistory(history); 123 } 124 125 /** 126 * replies the history managed by this model 127 * @return the history 128 */ 129 public History getHistory() { 130 return history; 131 } 132 133 protected boolean hasNewNodes(Way way) { 134 for (Node n: way.getNodes()) { 135 if (n.isNew()) return true; 136 } 137 return false; 138 } 139 protected boolean canShowAsLatest(OsmPrimitive primitive) { 140 if (primitive == null) return false; 141 if (primitive.isNew() || !primitive.isUsable()) return false; 142 143 //try creating a history primitive. if that fails, the primitive cannot be used. 144 try { 145 HistoryOsmPrimitive.forOsmPrimitive(primitive); 146 } catch (Exception ign) { 147 return false; 148 } 149 150 if (history == null) return false; 151 // only show latest of the same version if it is modified 152 if (history.getByVersion(primitive.getVersion()) != null) 153 return primitive.isModified(); 154 155 // if latest version from history is higher than a non existing primitive version, 156 // that means this version has been redacted and the primitive cannot be used. 157 if (history.getLatest().getVersion() > primitive.getVersion()) 158 return false; 159 160 // latest has a higher version than one of the primitives 161 // in the history (probably because the history got out of sync 162 // with uploaded data) -> show the primitive as latest 163 return true; 164 } 165 166 /** 167 * sets the history to be managed by this model 168 * 169 * @param history the history 170 * 171 */ 172 public void setHistory(History history) { 173 this.history = history; 174 if (history.getNumVersions() > 0) { 175 HistoryOsmPrimitive newLatest = null; 176 OsmDataLayer editLayer = Main.main.getEditLayer(); 177 if (editLayer != null) { 178 OsmPrimitive p = editLayer.data.getPrimitiveById(history.getId(), history.getType()); 179 if (canShowAsLatest(p)) { 180 newLatest = new HistoryPrimitiveBuilder().build(p); 181 } 182 } 183 if (newLatest == null) { 184 current = history.getLatest(); 185 int prevIndex = history.getNumVersions() - 2; 186 reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex); 187 } else { 188 reference = history.getLatest(); 189 current = newLatest; 190 } 191 setLatest(newLatest); 192 } 193 initTagTableModels(); 194 fireModelChange(); 195 } 196 197 protected void fireModelChange() { 198 initNodeListTableModels(); 199 initMemberListTableModels(); 200 setChanged(); 201 notifyObservers(); 202 versionTableModel.fireTableDataChanged(); 203 } 204 205 /** 206 * Replies the table model to be used in a {@link JTable} which 207 * shows the list of versions in this history. 208 * 209 * @return the table model 210 */ 211 public VersionTableModel getVersionTableModel() { 212 return versionTableModel; 213 } 214 215 protected void initTagTableModels() { 216 currentTagTableModel.initKeyList(); 217 referenceTagTableModel.initKeyList(); 218 } 219 220 /** 221 * Should be called everytime either reference of current changes to update the diff. 222 * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels 223 */ 224 protected void initNodeListTableModels() { 225 226 if(current.getType() != OsmPrimitiveType.WAY || reference.getType() != OsmPrimitiveType.WAY) 227 return; 228 TwoColumnDiff diff = new TwoColumnDiff( 229 ((HistoryWay)reference).getNodes().toArray(), 230 ((HistoryWay)current).getNodes().toArray()); 231 referenceNodeListTableModel.setRows(diff.referenceDiff); 232 currentNodeListTableModel.setRows(diff.currentDiff); 233 234 referenceNodeListTableModel.fireTableDataChanged(); 235 currentNodeListTableModel.fireTableDataChanged(); 236 } 237 238 protected void initMemberListTableModels() { 239 if(current.getType() != OsmPrimitiveType.RELATION || reference.getType() != OsmPrimitiveType.RELATION) 240 return; 241 242 TwoColumnDiff diff = new TwoColumnDiff( 243 ((HistoryRelation)reference).getMembers().toArray(), 244 ((HistoryRelation)current).getMembers().toArray()); 245 246 referenceRelationMemberTableModel.setRows(diff.referenceDiff); 247 currentRelationMemberTableModel.setRows(diff.currentDiff); 248 249 currentRelationMemberTableModel.fireTableDataChanged(); 250 referenceRelationMemberTableModel.fireTableDataChanged(); 251 } 252 253 /** 254 * replies the tag table model for the respective point in time 255 * 256 * @param pointInTimeType the type of the point in time (must not be null) 257 * @return the tag table model 258 * @exception IllegalArgumentException thrown, if pointInTimeType is null 259 */ 260 public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException { 261 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 262 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 263 return currentTagTableModel; 264 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 265 return referenceTagTableModel; 266 267 // should not happen 268 return null; 269 } 270 271 public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException { 272 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 273 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 274 return currentNodeListTableModel; 275 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 276 return referenceNodeListTableModel; 277 278 // should not happen 279 return null; 280 } 281 282 public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException { 283 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 284 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 285 return currentRelationMemberTableModel; 286 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 287 return referenceRelationMemberTableModel; 288 289 // should not happen 290 return null; 291 } 292 293 /** 294 * Sets the {@link HistoryOsmPrimitive} which plays the role of a reference point 295 * in time (see {@link PointInTimeType}). 296 * 297 * @param reference the reference history primitive. Must not be null. 298 * @throws IllegalArgumentException thrown if reference is null 299 * @throws IllegalStateException thrown if this model isn't a assigned a history yet 300 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 301 * 302 * @see #setHistory(History) 303 * @see PointInTimeType 304 */ 305 public void setReferencePointInTime(HistoryOsmPrimitive reference) throws IllegalArgumentException, IllegalStateException{ 306 CheckParameterUtil.ensureParameterNotNull(reference, "reference"); 307 if (history == null) 308 throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive.")); 309 if (reference.getId() != history.getId()) 310 throw new IllegalArgumentException(tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(), history.getId())); 311 HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion()); 312 if (primitive == null) 313 throw new IllegalArgumentException(tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion())); 314 315 this.reference = reference; 316 initTagTableModels(); 317 initNodeListTableModels(); 318 initMemberListTableModels(); 319 setChanged(); 320 notifyObservers(); 321 } 322 323 /** 324 * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point 325 * in time (see {@link PointInTimeType}). 326 * 327 * @param current the reference history primitive. Must not be {@code null}. 328 * @throws IllegalArgumentException thrown if reference is {@code null} 329 * @throws IllegalStateException thrown if this model isn't a assigned a history yet 330 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 331 * 332 * @see #setHistory(History) 333 * @see PointInTimeType 334 */ 335 public void setCurrentPointInTime(HistoryOsmPrimitive current) throws IllegalArgumentException, IllegalStateException{ 336 CheckParameterUtil.ensureParameterNotNull(current, "current"); 337 if (history == null) 338 throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive.")); 339 if (current.getId() != history.getId()) 340 throw new IllegalArgumentException(tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(), history.getId())); 341 HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion()); 342 if (primitive == null) 343 throw new IllegalArgumentException(tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion())); 344 this.current = current; 345 initTagTableModels(); 346 initNodeListTableModels(); 347 initMemberListTableModels(); 348 setChanged(); 349 notifyObservers(); 350 } 351 352 /** 353 * Replies the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} 354 * 355 * @return the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} (may be null) 356 */ 357 public HistoryOsmPrimitive getCurrentPointInTime() { 358 return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME); 359 } 360 361 /** 362 * Replies the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 363 * 364 * @return the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null) 365 */ 366 public HistoryOsmPrimitive getReferencePointInTime() { 367 return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME); 368 } 369 370 /** 371 * replies the history OSM primitive for a given point in time 372 * 373 * @param type the type of the point in time (must not be null) 374 * @return the respective primitive. Can be null. 375 * @exception IllegalArgumentException thrown, if type is null 376 */ 377 public HistoryOsmPrimitive getPointInTime(PointInTimeType type) throws IllegalArgumentException { 378 CheckParameterUtil.ensureParameterNotNull(type, "type"); 379 if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 380 return current; 381 else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 382 return reference; 383 384 // should not happen 385 return null; 386 } 387 388 /** 389 * Returns true if <code>primitive</code> is the latest primitive 390 * representing the version currently edited in the current data 391 * layer. 392 * 393 * @param primitive the primitive to check 394 * @return true if <code>primitive</code> is the latest primitive 395 */ 396 public boolean isLatest(HistoryOsmPrimitive primitive) { 397 if (primitive == null) return false; 398 return primitive == latest; 399 } 400 401 /** 402 * The table model for the list of versions in the current history 403 * 404 */ 405 public final class VersionTableModel extends AbstractTableModel { 406 407 private VersionTableModel() { 408 } 409 410 @Override 411 public int getRowCount() { 412 if (history == null) 413 return 0; 414 int ret = history.getNumVersions(); 415 if (latest != null) { 416 ret++; 417 } 418 return ret; 419 } 420 421 @Override 422 public Object getValueAt(int row, int column) { 423 switch (column) { 424 case 0: 425 return Long.toString(getPrimitive(row).getVersion()); 426 case 1: 427 return isReferencePointInTime(row); 428 case 2: 429 return isCurrentPointInTime(row); 430 case 3: 431 HistoryOsmPrimitive p3 = getPrimitive(row); 432 if (p3 != null && p3.getTimestamp() != null) 433 return DateUtils.formatDateTime(p3.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT); 434 return null; 435 case 4: 436 HistoryOsmPrimitive p4 = getPrimitive(row); 437 if (p4 != null) { 438 User user = p4.getUser(); 439 if (user != null) 440 return user.getName(); 441 } 442 return null; 443 } 444 return null; 445 } 446 447 @Override 448 public void setValueAt(Object aValue, int row, int column) { 449 if (!((Boolean) aValue)) return; 450 switch (column) { 451 case 1: 452 setReferencePointInTime(row); 453 break; 454 case 2: 455 setCurrentPointInTime(row); 456 break; 457 default: 458 return; 459 } 460 fireTableDataChanged(); 461 } 462 463 @Override 464 public boolean isCellEditable(int row, int column) { 465 return column >= 1 && column <= 2; 466 } 467 468 public void setReferencePointInTime(int row) { 469 if (history == null) return; 470 if (row == history.getNumVersions()) { 471 if (latest != null) { 472 HistoryBrowserModel.this.setReferencePointInTime(latest); 473 } 474 return; 475 } 476 if (row < 0 || row > history.getNumVersions()) return; 477 HistoryOsmPrimitive reference = history.get(row); 478 HistoryBrowserModel.this.setReferencePointInTime(reference); 479 } 480 481 public void setCurrentPointInTime(int row) { 482 if (history == null) return; 483 if (row == history.getNumVersions()) { 484 if (latest != null) { 485 HistoryBrowserModel.this.setCurrentPointInTime(latest); 486 } 487 return; 488 } 489 if (row < 0 || row > history.getNumVersions()) return; 490 HistoryOsmPrimitive current = history.get(row); 491 HistoryBrowserModel.this.setCurrentPointInTime(current); 492 } 493 494 public boolean isReferencePointInTime(int row) { 495 if (history == null) return false; 496 if (row == history.getNumVersions()) 497 return latest == reference; 498 if (row < 0 || row > history.getNumVersions()) return false; 499 HistoryOsmPrimitive p = history.get(row); 500 return p == reference; 501 } 502 503 public boolean isCurrentPointInTime(int row) { 504 if (history == null) return false; 505 if (row == history.getNumVersions()) 506 return latest == current; 507 if (row < 0 || row > history.getNumVersions()) return false; 508 HistoryOsmPrimitive p = history.get(row); 509 return p == current; 510 } 511 512 public HistoryOsmPrimitive getPrimitive(int row) { 513 if (history == null) 514 return null; 515 return isLatest(row) ? latest : history.get(row); 516 } 517 518 public boolean isLatest(int row) { 519 return row >= history.getNumVersions(); 520 } 521 522 public OsmPrimitive getLatest() { 523 if (latest == null) return null; 524 OsmDataLayer editLayer = Main.main.getEditLayer(); 525 if (editLayer == null) return null; 526 return editLayer.data.getPrimitiveById(latest.getId(), latest.getType()); 527 } 528 529 @Override 530 public int getColumnCount() { 531 return 6; 532 } 533 } 534 535 /** 536 * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 537 * or {@link PointInTimeType#CURRENT_POINT_IN_TIME} 538 * 539 */ 540 public class TagTableModel extends AbstractTableModel { 541 542 private List<String> keys; 543 private PointInTimeType pointInTimeType; 544 545 protected void initKeyList() { 546 HashSet<String> keySet = new HashSet<>(); 547 if (current != null) { 548 keySet.addAll(current.getTags().keySet()); 549 } 550 if (reference != null) { 551 keySet.addAll(reference.getTags().keySet()); 552 } 553 keys = new ArrayList<>(keySet); 554 Collections.sort(keys); 555 fireTableDataChanged(); 556 } 557 558 protected TagTableModel(PointInTimeType type) { 559 pointInTimeType = type; 560 initKeyList(); 561 } 562 563 @Override 564 public int getRowCount() { 565 if (keys == null) return 0; 566 return keys.size(); 567 } 568 569 @Override 570 public Object getValueAt(int row, int column) { 571 return keys.get(row); 572 } 573 574 @Override 575 public boolean isCellEditable(int row, int column) { 576 return false; 577 } 578 579 public boolean hasTag(String key) { 580 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 581 if (primitive == null) 582 return false; 583 return primitive.hasTag(key); 584 } 585 586 public String getValue(String key) { 587 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 588 if (primitive == null) 589 return null; 590 return primitive.get(key); 591 } 592 593 public boolean oppositeHasTag(String key) { 594 PointInTimeType opposite = pointInTimeType.opposite(); 595 HistoryOsmPrimitive primitive = getPointInTime(opposite); 596 if (primitive == null) 597 return false; 598 return primitive.hasTag(key); 599 } 600 601 public String getOppositeValue(String key) { 602 PointInTimeType opposite = pointInTimeType.opposite(); 603 HistoryOsmPrimitive primitive = getPointInTime(opposite); 604 if (primitive == null) 605 return null; 606 return primitive.get(key); 607 } 608 609 public boolean hasSameValueAsOpposite(String key) { 610 String value = getValue(key); 611 String oppositeValue = getOppositeValue(key); 612 if (value == null || oppositeValue == null) 613 return false; 614 return value.equals(oppositeValue); 615 } 616 617 public PointInTimeType getPointInTimeType() { 618 return pointInTimeType; 619 } 620 621 public boolean isCurrentPointInTime() { 622 return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME); 623 } 624 625 public boolean isReferencePointInTime() { 626 return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME); 627 } 628 629 @Override 630 public int getColumnCount() { 631 return 1; 632 } 633 } 634 635 protected void setLatest(HistoryOsmPrimitive latest) { 636 if (latest == null) { 637 if (this.current == this.latest) { 638 this.current = history.getLatest(); 639 } 640 if (this.reference == this.latest) { 641 this.current = history.getLatest(); 642 } 643 this.latest = null; 644 } else { 645 if (this.current == this.latest) { 646 this.current = latest; 647 } 648 if (this.reference == this.latest) { 649 this.reference = latest; 650 } 651 this.latest = latest; 652 } 653 fireModelChange(); 654 } 655 656 /** 657 * Removes this model as listener for data change and layer change 658 * events. 659 * 660 */ 661 public void unlinkAsListener() { 662 OsmDataLayer editLayer = Main.main.getEditLayer(); 663 if (editLayer != null) { 664 editLayer.data.removeDataSetListener(this); 665 } 666 MapView.removeLayerChangeListener(this); 667 } 668 669 /* ---------------------------------------------------------------------- */ 670 /* DataSetListener */ 671 /* ---------------------------------------------------------------------- */ 672 @Override 673 public void nodeMoved(NodeMovedEvent event) { 674 Node node = event.getNode(); 675 if (!node.isNew() && node.getId() == history.getId()) { 676 setLatest(new HistoryPrimitiveBuilder().build(node)); 677 } 678 } 679 680 @Override 681 public void primitivesAdded(PrimitivesAddedEvent event) { 682 for (OsmPrimitive p: event.getPrimitives()) { 683 if (canShowAsLatest(p)) { 684 setLatest(new HistoryPrimitiveBuilder().build(p)); 685 } 686 } 687 } 688 689 @Override 690 public void primitivesRemoved(PrimitivesRemovedEvent event) { 691 for (OsmPrimitive p: event.getPrimitives()) { 692 if (!p.isNew() && p.getId() == history.getId()) { 693 setLatest(null); 694 } 695 } 696 } 697 698 @Override 699 public void relationMembersChanged(RelationMembersChangedEvent event) { 700 Relation r = event.getRelation(); 701 if (!r.isNew() && r.getId() == history.getId()) { 702 setLatest(new HistoryPrimitiveBuilder().build(r)); 703 } 704 } 705 706 @Override 707 public void tagsChanged(TagsChangedEvent event) { 708 OsmPrimitive prim = event.getPrimitive(); 709 if (!prim.isNew() && prim.getId() == history.getId()) { 710 setLatest(new HistoryPrimitiveBuilder().build(prim)); 711 } 712 } 713 714 @Override 715 public void wayNodesChanged(WayNodesChangedEvent event) { 716 Way way = event.getChangedWay(); 717 if (!way.isNew() && way.getId() == history.getId()) { 718 setLatest(new HistoryPrimitiveBuilder().build(way)); 719 } 720 } 721 722 @Override 723 public void dataChanged(DataChangedEvent event) { 724 OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType()); 725 HistoryOsmPrimitive latest; 726 if (canShowAsLatest(primitive)) { 727 latest = new HistoryPrimitiveBuilder().build(primitive); 728 } else { 729 latest = null; 730 } 731 setLatest(latest); 732 fireModelChange(); 733 } 734 735 @Override 736 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 737 // Irrelevant 738 } 739 740 /* ---------------------------------------------------------------------- */ 741 /* LayerChangeListener */ 742 /* ---------------------------------------------------------------------- */ 743 @Override 744 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 745 if (oldLayer instanceof OsmDataLayer) { 746 OsmDataLayer l = (OsmDataLayer)oldLayer; 747 l.data.removeDataSetListener(this); 748 } 749 if (!(newLayer instanceof OsmDataLayer)) { 750 latest = null; 751 fireModelChange(); 752 return; 753 } 754 OsmDataLayer l = (OsmDataLayer)newLayer; 755 l.data.addDataSetListener(this); 756 OsmPrimitive primitive = l.data.getPrimitiveById(history.getId(), history.getType()); 757 HistoryOsmPrimitive latest; 758 if (canShowAsLatest(primitive)) { 759 latest = new HistoryPrimitiveBuilder().build(primitive); 760 } else { 761 latest = null; 762 } 763 setLatest(latest); 764 fireModelChange(); 765 } 766 767 @Override 768 public void layerAdded(Layer newLayer) {} 769 @Override 770 public void layerRemoved(Layer oldLayer) {} 771 772 /** 773 * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive} 774 * 775 */ 776 static class HistoryPrimitiveBuilder extends AbstractVisitor { 777 private HistoryOsmPrimitive clone; 778 779 @Override 780 public void visit(Node n) { 781 clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false); 782 clone.setTags(n.getKeys()); 783 } 784 785 @Override 786 public void visit(Relation r) { 787 clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false); 788 clone.setTags(r.getKeys()); 789 HistoryRelation hr = (HistoryRelation)clone; 790 for (RelationMember rm : r.getMembers()) { 791 hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId())); 792 } 793 } 794 795 @Override 796 public void visit(Way w) { 797 clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false); 798 clone.setTags(w.getKeys()); 799 for (Node n: w.getNodes()) { 800 ((HistoryWay)clone).addNode(n.getUniqueId()); 801 } 802 } 803 804 private User getCurrentUser() { 805 UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo(); 806 return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName()); 807 } 808 809 public HistoryOsmPrimitive build(OsmPrimitive primitive) { 810 primitive.accept(this); 811 return clone; 812 } 813 } 814}