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