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