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 Main.trace(ign); 147 return false; 148 } 149 150 if (history == null) 151 return false; 152 // only show latest of the same version if it is modified 153 if (history.getByVersion(primitive.getVersion()) != null) 154 return primitive.isModified(); 155 156 // if latest version from history is higher than a non existing primitive version, 157 // that means this version has been redacted and the primitive cannot be used. 158 if (history.getLatest().getVersion() > primitive.getVersion()) 159 return false; 160 161 // latest has a higher version than one of the primitives 162 // in the history (probably because the history got out of sync 163 // with uploaded data) -> show the primitive as latest 164 return true; 165 } 166 167 /** 168 * sets the history to be managed by this model 169 * 170 * @param history the history 171 * 172 */ 173 public void setHistory(History history) { 174 this.history = history; 175 if (history.getNumVersions() > 0) { 176 HistoryOsmPrimitive newLatest = null; 177 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 178 if (editLayer != null) { 179 OsmPrimitive p = editLayer.data.getPrimitiveById(history.getId(), history.getType()); 180 if (canShowAsLatest(p)) { 181 newLatest = new HistoryPrimitiveBuilder().build(p); 182 } 183 } 184 if (newLatest == null) { 185 current = history.getLatest(); 186 int prevIndex = history.getNumVersions() - 2; 187 reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex); 188 } else { 189 reference = history.getLatest(); 190 current = newLatest; 191 } 192 setLatest(newLatest); 193 } 194 initTagTableModels(); 195 fireModelChange(); 196 } 197 198 protected void fireModelChange() { 199 initNodeListTableModels(); 200 initMemberListTableModels(); 201 fireStateChanged(); 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 if (current == null || current.getType() != OsmPrimitiveType.WAY 226 || reference == null || 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, diff.referenceReversed); 232 currentNodeListTableModel.setRows(diff.currentDiff, false); 233 } 234 235 protected void initMemberListTableModels() { 236 if (current == null || current.getType() != OsmPrimitiveType.RELATION 237 || reference == null || reference.getType() != OsmPrimitiveType.RELATION) 238 return; 239 TwoColumnDiff diff = new TwoColumnDiff( 240 ((HistoryRelation) reference).getMembers().toArray(), 241 ((HistoryRelation) current).getMembers().toArray()); 242 referenceRelationMemberTableModel.setRows(diff.referenceDiff, diff.referenceReversed); 243 currentRelationMemberTableModel.setRows(diff.currentDiff, false); 244 } 245 246 /** 247 * Replies the tag table model for the respective point in time. 248 * 249 * @param pointInTimeType the type of the point in time (must not be null) 250 * @return the tag table model 251 * @throws IllegalArgumentException if pointInTimeType is null 252 */ 253 public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) { 254 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 255 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 256 return currentTagTableModel; 257 else // REFERENCE_POINT_IN_TIME 258 return referenceTagTableModel; 259 } 260 261 /** 262 * Replies the node list table model for the respective point in time. 263 * 264 * @param pointInTimeType the type of the point in time (must not be null) 265 * @return the node list table model 266 * @throws IllegalArgumentException if pointInTimeType is null 267 */ 268 public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) { 269 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 270 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 271 return currentNodeListTableModel; 272 else // REFERENCE_POINT_IN_TIME 273 return referenceNodeListTableModel; 274 } 275 276 /** 277 * Replies the relation member table model for the respective point in time. 278 * 279 * @param pointInTimeType the type of the point in time (must not be null) 280 * @return the relation member table model 281 * @throws IllegalArgumentException if pointInTimeType is null 282 */ 283 public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) { 284 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 285 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 286 return currentRelationMemberTableModel; 287 else // REFERENCE_POINT_IN_TIME 288 return referenceRelationMemberTableModel; 289 } 290 291 /** 292 * Sets the {@link HistoryOsmPrimitive} which plays the role of a reference point 293 * in time (see {@link PointInTimeType}). 294 * 295 * @param reference the reference history primitive. Must not be null. 296 * @throws IllegalArgumentException if reference is null 297 * @throws IllegalStateException if this model isn't a assigned a history yet 298 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 299 * 300 * @see #setHistory(History) 301 * @see PointInTimeType 302 */ 303 public void setReferencePointInTime(HistoryOsmPrimitive reference) { 304 CheckParameterUtil.ensureParameterNotNull(reference, "reference"); 305 if (history == null) 306 throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive.")); 307 if (reference.getId() != history.getId()) 308 throw new IllegalArgumentException( 309 tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(), history.getId())); 310 HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion()); 311 if (primitive == null) 312 throw new IllegalArgumentException( 313 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 fireStateChanged(); 320 } 321 322 /** 323 * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point 324 * in time (see {@link PointInTimeType}). 325 * 326 * @param current the reference history primitive. Must not be {@code null}. 327 * @throws IllegalArgumentException if reference is {@code null} 328 * @throws IllegalStateException if this model isn't a assigned a history yet 329 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 330 * 331 * @see #setHistory(History) 332 * @see PointInTimeType 333 */ 334 public void setCurrentPointInTime(HistoryOsmPrimitive current) { 335 CheckParameterUtil.ensureParameterNotNull(current, "current"); 336 if (history == null) 337 throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive.")); 338 if (current.getId() != history.getId()) 339 throw new IllegalArgumentException( 340 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( 344 tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion())); 345 this.current = current; 346 initTagTableModels(); 347 initNodeListTableModels(); 348 initMemberListTableModels(); 349 fireStateChanged(); 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 * @throws IllegalArgumentException if type is null 376 */ 377 public HistoryOsmPrimitive getPointInTime(PointInTimeType type) { 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) 398 return false; 399 return primitive == latest; 400 } 401 402 /** 403 * The table model for the list of versions in the current history 404 * 405 */ 406 public final class VersionTableModel extends AbstractTableModel { 407 408 private VersionTableModel() { 409 } 410 411 @Override 412 public int getRowCount() { 413 if (history == null) 414 return 0; 415 int ret = history.getNumVersions(); 416 if (latest != null) { 417 ret++; 418 } 419 return ret; 420 } 421 422 @Override 423 public Object getValueAt(int row, int column) { 424 switch (column) { 425 case 0: 426 return Long.toString(getPrimitive(row).getVersion()); 427 case 1: 428 return isReferencePointInTime(row); 429 case 2: 430 return isCurrentPointInTime(row); 431 case 3: 432 HistoryOsmPrimitive p3 = getPrimitive(row); 433 if (p3 != null && p3.getTimestamp() != null) 434 return DateUtils.formatDateTime(p3.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT); 435 return null; 436 case 4: 437 HistoryOsmPrimitive p4 = getPrimitive(row); 438 if (p4 != null) { 439 User user = p4.getUser(); 440 if (user != null) 441 return user.getName(); 442 } 443 return null; 444 } 445 return null; 446 } 447 448 @Override 449 public void setValueAt(Object aValue, int row, int column) { 450 if (!((Boolean) aValue)) 451 return; 452 switch (column) { 453 case 1: 454 setReferencePointInTime(row); 455 break; 456 case 2: 457 setCurrentPointInTime(row); 458 break; 459 default: 460 return; 461 } 462 fireTableDataChanged(); 463 } 464 465 @Override 466 public boolean isCellEditable(int row, int column) { 467 return column >= 1 && column <= 2; 468 } 469 470 public void setReferencePointInTime(int row) { 471 if (history == null) 472 return; 473 if (row == history.getNumVersions()) { 474 if (latest != null) { 475 HistoryBrowserModel.this.setReferencePointInTime(latest); 476 } 477 return; 478 } 479 if (row < 0 || row > history.getNumVersions()) 480 return; 481 HistoryOsmPrimitive reference = history.get(row); 482 HistoryBrowserModel.this.setReferencePointInTime(reference); 483 } 484 485 public void setCurrentPointInTime(int row) { 486 if (history == null) 487 return; 488 if (row == history.getNumVersions()) { 489 if (latest != null) { 490 HistoryBrowserModel.this.setCurrentPointInTime(latest); 491 } 492 return; 493 } 494 if (row < 0 || row > history.getNumVersions()) 495 return; 496 HistoryOsmPrimitive current = history.get(row); 497 HistoryBrowserModel.this.setCurrentPointInTime(current); 498 } 499 500 public boolean isReferencePointInTime(int row) { 501 if (history == null) 502 return false; 503 if (row == history.getNumVersions()) 504 return latest == reference; 505 if (row < 0 || row > history.getNumVersions()) 506 return false; 507 HistoryOsmPrimitive p = history.get(row); 508 return p == reference; 509 } 510 511 public boolean isCurrentPointInTime(int row) { 512 if (history == null) 513 return false; 514 if (row == history.getNumVersions()) 515 return latest == current; 516 if (row < 0 || row > history.getNumVersions()) 517 return false; 518 HistoryOsmPrimitive p = history.get(row); 519 return p == current; 520 } 521 522 public HistoryOsmPrimitive getPrimitive(int row) { 523 if (history == null) 524 return null; 525 return isLatest(row) ? latest : history.get(row); 526 } 527 528 public boolean isLatest(int row) { 529 return row >= history.getNumVersions(); 530 } 531 532 public OsmPrimitive getLatest() { 533 if (latest == null) 534 return null; 535 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 536 if (editLayer == null) 537 return null; 538 return editLayer.data.getPrimitiveById(latest.getId(), latest.getType()); 539 } 540 541 @Override 542 public int getColumnCount() { 543 return 6; 544 } 545 } 546 547 /** 548 * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 549 * or {@link PointInTimeType#CURRENT_POINT_IN_TIME} 550 * 551 */ 552 public class TagTableModel extends AbstractTableModel { 553 554 private List<String> keys; 555 private final PointInTimeType pointInTimeType; 556 557 protected TagTableModel(PointInTimeType type) { 558 pointInTimeType = type; 559 initKeyList(); 560 } 561 562 protected void initKeyList() { 563 Set<String> keySet = new HashSet<>(); 564 if (current != null) { 565 keySet.addAll(current.getTags().keySet()); 566 } 567 if (reference != null) { 568 keySet.addAll(reference.getTags().keySet()); 569 } 570 keys = new ArrayList<>(keySet); 571 Collections.sort(keys); 572 fireTableDataChanged(); 573 } 574 575 @Override 576 public int getRowCount() { 577 if (keys == null) 578 return 0; 579 return keys.size(); 580 } 581 582 @Override 583 public Object getValueAt(int row, int column) { 584 return getKeyAt(row); 585 } 586 587 /** 588 * Get the key for the given row. 589 * @param row The row 590 * @return The key in that row. 591 * @since 10637 592 */ 593 public String getKeyAt(int row) { 594 return keys.get(row); 595 } 596 597 public boolean hasTag(String key) { 598 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 599 if (primitive == null) 600 return false; 601 return primitive.hasTag(key); 602 } 603 604 public String getValue(String key) { 605 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType); 606 if (primitive == null) 607 return null; 608 return primitive.get(key); 609 } 610 611 public boolean oppositeHasTag(String key) { 612 PointInTimeType opposite = pointInTimeType.opposite(); 613 HistoryOsmPrimitive primitive = getPointInTime(opposite); 614 if (primitive == null) 615 return false; 616 return primitive.hasTag(key); 617 } 618 619 public String getOppositeValue(String key) { 620 PointInTimeType opposite = pointInTimeType.opposite(); 621 HistoryOsmPrimitive primitive = getPointInTime(opposite); 622 if (primitive == null) 623 return null; 624 return primitive.get(key); 625 } 626 627 public boolean hasSameValueAsOpposite(String key) { 628 String value = getValue(key); 629 String oppositeValue = getOppositeValue(key); 630 if (value == null || oppositeValue == null) 631 return false; 632 return value.equals(oppositeValue); 633 } 634 635 public PointInTimeType getPointInTimeType() { 636 return pointInTimeType; 637 } 638 639 public boolean isCurrentPointInTime() { 640 return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME); 641 } 642 643 public boolean isReferencePointInTime() { 644 return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME); 645 } 646 647 @Override 648 public int getColumnCount() { 649 return 2; 650 } 651 } 652 653 protected void setLatest(HistoryOsmPrimitive latest) { 654 if (latest == null) { 655 if (this.current == this.latest) { 656 this.current = history != null ? history.getLatest() : null; 657 } 658 if (this.reference == this.latest) { 659 this.reference = history != null ? history.getLatest() : null; 660 } 661 this.latest = null; 662 } else { 663 if (this.current == this.latest) { 664 this.current = latest; 665 } 666 if (this.reference == this.latest) { 667 this.reference = latest; 668 } 669 this.latest = latest; 670 } 671 fireModelChange(); 672 } 673 674 /** 675 * Removes this model as listener for data change and layer change events. 676 * 677 */ 678 public void unlinkAsListener() { 679 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 680 if (editLayer != null) { 681 editLayer.data.removeDataSetListener(this); 682 } 683 Main.getLayerManager().removeActiveLayerChangeListener(this); 684 } 685 686 /* ---------------------------------------------------------------------- */ 687 /* DataSetListener */ 688 /* ---------------------------------------------------------------------- */ 689 @Override 690 public void nodeMoved(NodeMovedEvent event) { 691 Node node = event.getNode(); 692 if (!node.isNew() && node.getId() == history.getId()) { 693 setLatest(new HistoryPrimitiveBuilder().build(node)); 694 } 695 } 696 697 @Override 698 public void primitivesAdded(PrimitivesAddedEvent event) { 699 for (OsmPrimitive p: event.getPrimitives()) { 700 if (canShowAsLatest(p)) { 701 setLatest(new HistoryPrimitiveBuilder().build(p)); 702 } 703 } 704 } 705 706 @Override 707 public void primitivesRemoved(PrimitivesRemovedEvent event) { 708 for (OsmPrimitive p: event.getPrimitives()) { 709 if (!p.isNew() && p.getId() == history.getId()) { 710 setLatest(null); 711 } 712 } 713 } 714 715 @Override 716 public void relationMembersChanged(RelationMembersChangedEvent event) { 717 Relation r = event.getRelation(); 718 if (!r.isNew() && r.getId() == history.getId()) { 719 setLatest(new HistoryPrimitiveBuilder().build(r)); 720 } 721 } 722 723 @Override 724 public void tagsChanged(TagsChangedEvent event) { 725 OsmPrimitive prim = event.getPrimitive(); 726 if (!prim.isNew() && prim.getId() == history.getId()) { 727 setLatest(new HistoryPrimitiveBuilder().build(prim)); 728 } 729 } 730 731 @Override 732 public void wayNodesChanged(WayNodesChangedEvent event) { 733 Way way = event.getChangedWay(); 734 if (!way.isNew() && way.getId() == history.getId()) { 735 setLatest(new HistoryPrimitiveBuilder().build(way)); 736 } 737 } 738 739 @Override 740 public void dataChanged(DataChangedEvent event) { 741 if (history == null) 742 return; 743 OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType()); 744 HistoryOsmPrimitive latest; 745 if (canShowAsLatest(primitive)) { 746 latest = new HistoryPrimitiveBuilder().build(primitive); 747 } else { 748 latest = null; 749 } 750 setLatest(latest); 751 fireModelChange(); 752 } 753 754 @Override 755 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 756 // Irrelevant 757 } 758 759 /* ---------------------------------------------------------------------- */ 760 /* ActiveLayerChangeListener */ 761 /* ---------------------------------------------------------------------- */ 762 @Override 763 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 764 Layer oldLayer = e.getPreviousActiveLayer(); 765 if (oldLayer instanceof OsmDataLayer) { 766 OsmDataLayer l = (OsmDataLayer) oldLayer; 767 l.data.removeDataSetListener(this); 768 } 769 Layer newLayer = e.getSource().getActiveLayer(); 770 if (!(newLayer instanceof OsmDataLayer)) { 771 latest = null; 772 fireModelChange(); 773 return; 774 } 775 OsmDataLayer l = (OsmDataLayer) newLayer; 776 l.data.addDataSetListener(this); 777 OsmPrimitive primitive = history != null ? l.data.getPrimitiveById(history.getId(), history.getType()) : null; 778 HistoryOsmPrimitive newLatest; 779 if (canShowAsLatest(primitive)) { 780 newLatest = new HistoryPrimitiveBuilder().build(primitive); 781 } else { 782 newLatest = null; 783 } 784 setLatest(newLatest); 785 fireModelChange(); 786 } 787 788 /** 789 * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive} 790 * 791 */ 792 static class HistoryPrimitiveBuilder extends AbstractVisitor { 793 private HistoryOsmPrimitive clone; 794 795 @Override 796 public void visit(Node n) { 797 clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false); 798 clone.setTags(n.getKeys()); 799 } 800 801 @Override 802 public void visit(Relation r) { 803 clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false); 804 clone.setTags(r.getKeys()); 805 HistoryRelation hr = (HistoryRelation) clone; 806 for (RelationMember rm : r.getMembers()) { 807 hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId())); 808 } 809 } 810 811 @Override 812 public void visit(Way w) { 813 clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false); 814 clone.setTags(w.getKeys()); 815 for (Node n: w.getNodes()) { 816 ((HistoryWay) clone).addNode(n.getUniqueId()); 817 } 818 } 819 820 private static User getCurrentUser() { 821 UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo(); 822 return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName()); 823 } 824 825 public HistoryOsmPrimitive build(OsmPrimitive primitive) { 826 primitive.accept(this); 827 return clone; 828 } 829 } 830 831}