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 }