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