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.Set; 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.layer.Layer; 044import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 045import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 046import org.openstreetmap.josm.gui.layer.OsmDataLayer; 047import org.openstreetmap.josm.gui.util.ChangeNotifier; 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 ChangeNotifier implements ActiveLayerChangeListener, 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.getLayerManager().getEditLayer(); 109 if (editLayer != null) { 110 editLayer.data.addDataSetListener(this); 111 } 112 } 113 Main.getLayerManager().addActiveLayerChangeListener(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) 138 return false; 139 if (primitive.isNew() || !primitive.isUsable()) 140 return false; 141 142 //try creating a history primitive. if that fails, the primitive cannot be used. 143 try { 144 HistoryOsmPrimitive.forOsmPrimitive(primitive); 145 } catch (IllegalArgumentException ign) { 146 return false; 147 } 148 149 if (history == null) 150 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.getLayerManager().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 fireStateChanged(); 201 versionTableModel.fireTableDataChanged(); 202 } 203 204 /** 205 * Replies the table model to be used in a {@link JTable} which 206 * shows the list of versions in this history. 207 * 208 * @return the table model 209 */ 210 public VersionTableModel getVersionTableModel() { 211 return versionTableModel; 212 } 213 214 protected void initTagTableModels() { 215 currentTagTableModel.initKeyList(); 216 referenceTagTableModel.initKeyList(); 217 } 218 219 /** 220 * Should be called everytime either reference of current changes to update the diff. 221 * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels 222 */ 223 protected void initNodeListTableModels() { 224 if (current == null || current.getType() != OsmPrimitiveType.WAY 225 || reference == null || reference.getType() != OsmPrimitiveType.WAY) 226 return; 227 TwoColumnDiff diff = new TwoColumnDiff( 228 ((HistoryWay) reference).getNodes().toArray(), 229 ((HistoryWay) current).getNodes().toArray()); 230 referenceNodeListTableModel.setRows(diff.referenceDiff, diff.referenceReversed); 231 currentNodeListTableModel.setRows(diff.currentDiff, false); 232 } 233 234 protected void initMemberListTableModels() { 235 if (current == null || current.getType() != OsmPrimitiveType.RELATION 236 || reference == null || reference.getType() != OsmPrimitiveType.RELATION) 237 return; 238 TwoColumnDiff diff = new TwoColumnDiff( 239 ((HistoryRelation) reference).getMembers().toArray(), 240 ((HistoryRelation) current).getMembers().toArray()); 241 referenceRelationMemberTableModel.setRows(diff.referenceDiff, diff.referenceReversed); 242 currentRelationMemberTableModel.setRows(diff.currentDiff, false); 243 } 244 245 /** 246 * Replies the tag table model for the respective point in time. 247 * 248 * @param pointInTimeType the type of the point in time (must not be null) 249 * @return the tag table model 250 * @throws IllegalArgumentException if pointInTimeType is null 251 */ 252 public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) { 253 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 254 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 255 return currentTagTableModel; 256 else // REFERENCE_POINT_IN_TIME 257 return referenceTagTableModel; 258 } 259 260 /** 261 * Replies the node list table model for the respective point in time. 262 * 263 * @param pointInTimeType the type of the point in time (must not be null) 264 * @return the node list table model 265 * @throws IllegalArgumentException if pointInTimeType is null 266 */ 267 public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) { 268 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 269 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 270 return currentNodeListTableModel; 271 else // REFERENCE_POINT_IN_TIME 272 return referenceNodeListTableModel; 273 } 274 275 /** 276 * Replies the relation member table model for the respective point in time. 277 * 278 * @param pointInTimeType the type of the point in time (must not be null) 279 * @return the relation member table model 280 * @throws IllegalArgumentException if pointInTimeType is null 281 */ 282 public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) { 283 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 284 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 285 return currentRelationMemberTableModel; 286 else // REFERENCE_POINT_IN_TIME 287 return referenceRelationMemberTableModel; 288 } 289 290 /** 291 * Sets the {@link HistoryOsmPrimitive} which plays the role of a reference point 292 * in time (see {@link PointInTimeType}). 293 * 294 * @param reference the reference history primitive. Must not be null. 295 * @throws IllegalArgumentException if reference is null 296 * @throws IllegalStateException if this model isn't a assigned a history yet 297 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 298 * 299 * @see #setHistory(History) 300 * @see PointInTimeType 301 */ 302 public void setReferencePointInTime(HistoryOsmPrimitive reference) { 303 CheckParameterUtil.ensureParameterNotNull(reference, "reference"); 304 if (history == null) 305 throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive.")); 306 if (reference.getId() != history.getId()) 307 throw new IllegalArgumentException( 308 tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(), history.getId())); 309 HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion()); 310 if (primitive == null) 311 throw new IllegalArgumentException( 312 tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion())); 313 314 this.reference = reference; 315 initTagTableModels(); 316 initNodeListTableModels(); 317 initMemberListTableModels(); 318 fireStateChanged(); 319 } 320 321 /** 322 * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point 323 * in time (see {@link PointInTimeType}). 324 * 325 * @param current the reference history primitive. Must not be {@code null}. 326 * @throws IllegalArgumentException if reference is {@code null} 327 * @throws IllegalStateException if this model isn't a assigned a history yet 328 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 329 * 330 * @see #setHistory(History) 331 * @see PointInTimeType 332 */ 333 public void setCurrentPointInTime(HistoryOsmPrimitive current) { 334 CheckParameterUtil.ensureParameterNotNull(current, "current"); 335 if (history == null) 336 throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive.")); 337 if (current.getId() != history.getId()) 338 throw new IllegalArgumentException( 339 tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(), history.getId())); 340 HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion()); 341 if (primitive == null) 342 throw new IllegalArgumentException( 343 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 fireStateChanged(); 349 } 350 351 /** 352 * Replies the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} 353 * 354 * @return the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} (may be null) 355 */ 356 public HistoryOsmPrimitive getCurrentPointInTime() { 357 return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME); 358 } 359 360 /** 361 * Replies the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 362 * 363 * @return the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null) 364 */ 365 public HistoryOsmPrimitive getReferencePointInTime() { 366 return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME); 367 } 368 369 /** 370 * replies the history OSM primitive for a given point in time 371 * 372 * @param type the type of the point in time (must not be null) 373 * @return the respective primitive. Can be null. 374 * @throws IllegalArgumentException if type is null 375 */ 376 public HistoryOsmPrimitive getPointInTime(PointInTimeType type) { 377 CheckParameterUtil.ensureParameterNotNull(type, "type"); 378 if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 379 return current; 380 else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 381 return reference; 382 383 // should not happen 384 return null; 385 } 386 387 /** 388 * Returns true if <code>primitive</code> is the latest primitive 389 * representing the version currently edited in the current data 390 * layer. 391 * 392 * @param primitive the primitive to check 393 * @return true if <code>primitive</code> is the latest primitive 394 */ 395 public boolean isLatest(HistoryOsmPrimitive primitive) { 396 if (primitive == null) 397 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)) 450 return; 451 switch (column) { 452 case 1: 453 setReferencePointInTime(row); 454 break; 455 case 2: 456 setCurrentPointInTime(row); 457 break; 458 default: 459 return; 460 } 461 fireTableDataChanged(); 462 } 463 464 @Override 465 public boolean isCellEditable(int row, int column) { 466 return column >= 1 && column <= 2; 467 } 468 469 public void setReferencePointInTime(int row) { 470 if (history == null) 471 return; 472 if (row == history.getNumVersions()) { 473 if (latest != null) { 474 HistoryBrowserModel.this.setReferencePointInTime(latest); 475 } 476 return; 477 } 478 if (row < 0 || row > history.getNumVersions()) 479 return; 480 HistoryOsmPrimitive reference = history.get(row); 481 HistoryBrowserModel.this.setReferencePointInTime(reference); 482 } 483 484 public void setCurrentPointInTime(int row) { 485 if (history == null) 486 return; 487 if (row == history.getNumVersions()) { 488 if (latest != null) { 489 HistoryBrowserModel.this.setCurrentPointInTime(latest); 490 } 491 return; 492 } 493 if (row < 0 || row > history.getNumVersions()) 494 return; 495 HistoryOsmPrimitive current = history.get(row); 496 HistoryBrowserModel.this.setCurrentPointInTime(current); 497 } 498 499 public boolean isReferencePointInTime(int row) { 500 if (history == null) 501 return false; 502 if (row == history.getNumVersions()) 503 return latest == reference; 504 if (row < 0 || row > history.getNumVersions()) 505 return false; 506 HistoryOsmPrimitive p = history.get(row); 507 return p == reference; 508 } 509 510 public boolean isCurrentPointInTime(int row) { 511 if (history == null) 512 return false; 513 if (row == history.getNumVersions()) 514 return latest == current; 515 if (row < 0 || row > history.getNumVersions()) 516 return false; 517 HistoryOsmPrimitive p = history.get(row); 518 return p == current; 519 } 520 521 public HistoryOsmPrimitive getPrimitive(int row) { 522 if (history == null) 523 return null; 524 return isLatest(row) ? latest : history.get(row); 525 } 526 527 public boolean isLatest(int row) { 528 return row >= history.getNumVersions(); 529 } 530 531 public OsmPrimitive getLatest() { 532 if (latest == null) 533 return null; 534 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 535 if (editLayer == null) 536 return null; 537 return editLayer.data.getPrimitiveById(latest.getId(), latest.getType()); 538 } 539 540 @Override 541 public int getColumnCount() { 542 return 6; 543 } 544 } 545 546 /** 547 * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 548 * or {@link PointInTimeType#CURRENT_POINT_IN_TIME} 549 * 550 */ 551 public class TagTableModel extends AbstractTableModel { 552 553 private List<String> keys; 554 private final PointInTimeType pointInTimeType; 555 556 protected TagTableModel(PointInTimeType type) { 557 pointInTimeType = type; 558 initKeyList(); 559 } 560 561 protected void initKeyList() { 562 Set<String> keySet = new HashSet<>(); 563 if (current != null) { 564 keySet.addAll(current.getTags().keySet()); 565 } 566 if (reference != null) { 567 keySet.addAll(reference.getTags().keySet()); 568 } 569 keys = new ArrayList<>(keySet); 570 Collections.sort(keys); 571 fireTableDataChanged(); 572 } 573 574 @Override 575 public int getRowCount() { 576 if (keys == null) 577 return 0; 578 return keys.size(); 579 } 580 581 @Override 582 public Object getValueAt(int row, int column) { 583 return keys.get(row); 584 } 585 586 public boolean hasTag(String key) { 587 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 588 if (primitive == null) 589 return false; 590 return primitive.hasTag(key); 591 } 592 593 public String getValue(String key) { 594 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 595 if (primitive == null) 596 return null; 597 return primitive.get(key); 598 } 599 600 public boolean oppositeHasTag(String key) { 601 PointInTimeType opposite = pointInTimeType.opposite(); 602 HistoryOsmPrimitive primitive = getPointInTime(opposite); 603 if (primitive == null) 604 return false; 605 return primitive.hasTag(key); 606 } 607 608 public String getOppositeValue(String key) { 609 PointInTimeType opposite = pointInTimeType.opposite(); 610 HistoryOsmPrimitive primitive = getPointInTime(opposite); 611 if (primitive == null) 612 return null; 613 return primitive.get(key); 614 } 615 616 public boolean hasSameValueAsOpposite(String key) { 617 String value = getValue(key); 618 String oppositeValue = getOppositeValue(key); 619 if (value == null || oppositeValue == null) 620 return false; 621 return value.equals(oppositeValue); 622 } 623 624 public PointInTimeType getPointInTimeType() { 625 return pointInTimeType; 626 } 627 628 public boolean isCurrentPointInTime() { 629 return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME); 630 } 631 632 public boolean isReferencePointInTime() { 633 return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME); 634 } 635 636 @Override 637 public int getColumnCount() { 638 return 1; 639 } 640 } 641 642 protected void setLatest(HistoryOsmPrimitive latest) { 643 if (latest == null) { 644 if (this.current == this.latest) { 645 this.current = history != null ? history.getLatest() : null; 646 } 647 if (this.reference == this.latest) { 648 this.reference = history != null ? history.getLatest() : null; 649 } 650 this.latest = null; 651 } else { 652 if (this.current == this.latest) { 653 this.current = latest; 654 } 655 if (this.reference == this.latest) { 656 this.reference = latest; 657 } 658 this.latest = latest; 659 } 660 fireModelChange(); 661 } 662 663 /** 664 * Removes this model as listener for data change and layer change events. 665 * 666 */ 667 public void unlinkAsListener() { 668 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 669 if (editLayer != null) { 670 editLayer.data.removeDataSetListener(this); 671 } 672 Main.getLayerManager().removeActiveLayerChangeListener(this); 673 } 674 675 /* ---------------------------------------------------------------------- */ 676 /* DataSetListener */ 677 /* ---------------------------------------------------------------------- */ 678 @Override 679 public void nodeMoved(NodeMovedEvent event) { 680 Node node = event.getNode(); 681 if (!node.isNew() && node.getId() == history.getId()) { 682 setLatest(new HistoryPrimitiveBuilder().build(node)); 683 } 684 } 685 686 @Override 687 public void primitivesAdded(PrimitivesAddedEvent event) { 688 for (OsmPrimitive p: event.getPrimitives()) { 689 if (canShowAsLatest(p)) { 690 setLatest(new HistoryPrimitiveBuilder().build(p)); 691 } 692 } 693 } 694 695 @Override 696 public void primitivesRemoved(PrimitivesRemovedEvent event) { 697 for (OsmPrimitive p: event.getPrimitives()) { 698 if (!p.isNew() && p.getId() == history.getId()) { 699 setLatest(null); 700 } 701 } 702 } 703 704 @Override 705 public void relationMembersChanged(RelationMembersChangedEvent event) { 706 Relation r = event.getRelation(); 707 if (!r.isNew() && r.getId() == history.getId()) { 708 setLatest(new HistoryPrimitiveBuilder().build(r)); 709 } 710 } 711 712 @Override 713 public void tagsChanged(TagsChangedEvent event) { 714 OsmPrimitive prim = event.getPrimitive(); 715 if (!prim.isNew() && prim.getId() == history.getId()) { 716 setLatest(new HistoryPrimitiveBuilder().build(prim)); 717 } 718 } 719 720 @Override 721 public void wayNodesChanged(WayNodesChangedEvent event) { 722 Way way = event.getChangedWay(); 723 if (!way.isNew() && way.getId() == history.getId()) { 724 setLatest(new HistoryPrimitiveBuilder().build(way)); 725 } 726 } 727 728 @Override 729 public void dataChanged(DataChangedEvent event) { 730 if (history == null) 731 return; 732 OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType()); 733 HistoryOsmPrimitive latest; 734 if (canShowAsLatest(primitive)) { 735 latest = new HistoryPrimitiveBuilder().build(primitive); 736 } else { 737 latest = null; 738 } 739 setLatest(latest); 740 fireModelChange(); 741 } 742 743 @Override 744 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 745 // Irrelevant 746 } 747 748 /* ---------------------------------------------------------------------- */ 749 /* ActiveLayerChangeListener */ 750 /* ---------------------------------------------------------------------- */ 751 @Override 752 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 753 Layer oldLayer = e.getPreviousActiveLayer(); 754 if (oldLayer instanceof OsmDataLayer) { 755 OsmDataLayer l = (OsmDataLayer) oldLayer; 756 l.data.removeDataSetListener(this); 757 } 758 Layer newLayer = e.getSource().getActiveLayer(); 759 if (!(newLayer instanceof OsmDataLayer)) { 760 latest = null; 761 fireModelChange(); 762 return; 763 } 764 OsmDataLayer l = (OsmDataLayer) newLayer; 765 l.data.addDataSetListener(this); 766 OsmPrimitive primitive = history != null ? l.data.getPrimitiveById(history.getId(), history.getType()) : null; 767 HistoryOsmPrimitive newLatest; 768 if (canShowAsLatest(primitive)) { 769 newLatest = new HistoryPrimitiveBuilder().build(primitive); 770 } else { 771 newLatest = null; 772 } 773 setLatest(newLatest); 774 fireModelChange(); 775 } 776 777 /** 778 * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive} 779 * 780 */ 781 static class HistoryPrimitiveBuilder extends AbstractVisitor { 782 private HistoryOsmPrimitive clone; 783 784 @Override 785 public void visit(Node n) { 786 clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false); 787 clone.setTags(n.getKeys()); 788 } 789 790 @Override 791 public void visit(Relation r) { 792 clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false); 793 clone.setTags(r.getKeys()); 794 HistoryRelation hr = (HistoryRelation) clone; 795 for (RelationMember rm : r.getMembers()) { 796 hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId())); 797 } 798 } 799 800 @Override 801 public void visit(Way w) { 802 clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false); 803 clone.setTags(w.getKeys()); 804 for (Node n: w.getNodes()) { 805 ((HistoryWay) clone).addNode(n.getUniqueId()); 806 } 807 } 808 809 private static User getCurrentUser() { 810 UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo(); 811 return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName()); 812 } 813 814 public HistoryOsmPrimitive build(OsmPrimitive primitive) { 815 primitive.accept(this); 816 return clone; 817 } 818 } 819 820}