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