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