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