source: josm/trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java@ 5476

Last change on this file since 5476 was 5440, checked in by Don-vip, 12 years ago

fix #7716 - History shows same version number, wrong date, user, and CT for modified objects

  • Property svn:eol-style set to native
File size: 34.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.history;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.DateFormat;
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.Observable;
11
12import javax.swing.table.AbstractTableModel;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.osm.Node;
16import org.openstreetmap.josm.data.osm.OsmPrimitive;
17import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
18import org.openstreetmap.josm.data.osm.Relation;
19import org.openstreetmap.josm.data.osm.RelationMember;
20import org.openstreetmap.josm.data.osm.RelationMemberData;
21import org.openstreetmap.josm.data.osm.User;
22import org.openstreetmap.josm.data.osm.UserInfo;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
25import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
26import org.openstreetmap.josm.data.osm.event.DataSetListener;
27import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
28import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
29import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
30import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
31import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
32import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
33import org.openstreetmap.josm.data.osm.history.History;
34import org.openstreetmap.josm.data.osm.history.HistoryNode;
35import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
36import org.openstreetmap.josm.data.osm.history.HistoryRelation;
37import org.openstreetmap.josm.data.osm.history.HistoryWay;
38import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
39import org.openstreetmap.josm.gui.JosmUserIdentityManager;
40import org.openstreetmap.josm.gui.MapView;
41import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
42import org.openstreetmap.josm.gui.dialogs.UserListDialog;
43import org.openstreetmap.josm.gui.layer.Layer;
44import org.openstreetmap.josm.gui.layer.OsmDataLayer;
45import org.openstreetmap.josm.io.XmlWriter;
46import org.openstreetmap.josm.tools.CheckParameterUtil;
47
48/**
49 * This is the model used by the history browser.
50 *
51 * The model state consists of the following elements:
52 * <ul>
53 * <li>the {@link History} of a specific {@link OsmPrimitive}</li>
54 * <li>a dedicated version in this {@link History} called the {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
55 * <li>another version in this {@link History} called the {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li>
56 * <ul>
57 * {@link HistoryBrowser} always compares the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} with the
58 * {@link PointInTimeType#CURRENT_POINT_IN_TIME}.
59
60 * This model provides various {@link TableModel}s for {@link JTable}s used in {@link HistoryBrowser}, for
61 * instance:
62 * <ul>
63 * <li>{@link #getTagTableModel(PointInTimeType)} replies a {@link TableModel} for the tags of either of
64 * the two selected versions</li>
65 * <li>{@link #getNodeListTableModel(PointInTimeType)} replies a {@link TableModel} for the list of nodes of
66 * the two selected versions (if the current history provides information about a {@link Way}</li>
67 * <li> {@link #getRelationMemberTableModel(PointInTimeType)} replies a {@link TableModel} for the list of relation
68 * members of the two selected versions (if the current history provides information about a {@link Relation}</li>
69 * </ul>
70 *
71 * @see HistoryBrowser
72 */
73public class HistoryBrowserModel extends Observable implements LayerChangeListener, DataSetListener {
74 /** the history of an OsmPrimitive */
75 private History history;
76 private HistoryOsmPrimitive reference;
77 private HistoryOsmPrimitive current;
78 /**
79 * latest isn't a reference of history. It's a clone of the currently edited
80 * {@link OsmPrimitive} in the current edit layer.
81 */
82 private HistoryOsmPrimitive latest;
83
84 private VersionTableModel versionTableModel;
85 private TagTableModel currentTagTableModel;
86 private TagTableModel referenceTagTableModel;
87 private RelationMemberTableModel currentRelationMemberTableModel;
88 private RelationMemberTableModel referenceRelationMemberTableModel;
89 private DiffTableModel referenceNodeListTableModel;
90 private DiffTableModel currentNodeListTableModel;
91
92 /**
93 * constructor
94 */
95 public HistoryBrowserModel() {
96 versionTableModel = new VersionTableModel();
97 currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
98 referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
99 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}
Note: See TracBrowser for help on using the repository browser.