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

Last change on this file since 4602 was 4602, checked in by bastiK, 12 years ago

upgrade historic users to real users, so their CT status can be shown in the history panel

  • Property svn:eol-style set to native
File size: 37.9 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.SimpleDateFormat;
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Observable;
12
13import javax.swing.table.AbstractTableModel;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.osm.Node;
17import org.openstreetmap.josm.data.osm.OsmPrimitive;
18import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
19import org.openstreetmap.josm.data.osm.Relation;
20import org.openstreetmap.josm.data.osm.RelationMember;
21import org.openstreetmap.josm.data.osm.User;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
24import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
25import org.openstreetmap.josm.data.osm.event.DataSetListener;
26import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
27import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
28import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
29import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
30import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
31import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
32import org.openstreetmap.josm.data.osm.history.History;
33import org.openstreetmap.josm.data.osm.history.HistoryNode;
34import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
35import org.openstreetmap.josm.data.osm.history.HistoryRelation;
36import org.openstreetmap.josm.data.osm.history.HistoryWay;
37import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
38import org.openstreetmap.josm.gui.MapView;
39import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
40import org.openstreetmap.josm.gui.dialogs.UserListDialog;
41import org.openstreetmap.josm.gui.layer.Layer;
42import org.openstreetmap.josm.gui.layer.OsmDataLayer;
43import org.openstreetmap.josm.io.XmlWriter;
44import org.openstreetmap.josm.tools.CheckParameterUtil;
45import org.openstreetmap.josm.tools.Diff;
46
47/**
48 * This is the model used by the history browser.
49 *
50 * The model state consists of the following elements:
51 * <ul>
52 * <li>the {@see History} of a specific {@see OsmPrimitive}</li>
53 * <li>a dedicated version in this {@see History} called the {@see PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
54 * <li>another version in this {@see History} called the {@see PointInTimeType#CURRENT_POINT_IN_TIME}</li>
55 * <ul>
56 * {@see HistoryBrowser} always compares the {@see PointInTimeType#REFERENCE_POINT_IN_TIME} with the
57 * {@see PointInTimeType#CURRENT_POINT_IN_TIME}.
58
59 * This model provides various {@see TableModel}s for {@see JTable}s used in {@see HistoryBrowser}, for
60 * instance:
61 * <ul>
62 * <li>{@see #getTagTableModel(PointInTimeType)} replies a {@see TableModel} for the tags of either of
63 * the two selected versions</li>
64 * <li>{@see #getNodeListTableModel(PointInTimeType)} replies a {@see TableModel} for the list of nodes of
65 * the two selected versions (if the current history provides information about a {@see Way}</li>
66 * <li> {@see #getRelationMemberTableModel(PointInTimeType)} replies a {@see TableModel} for the list of relation
67 * members of the two selected versions (if the current history provides information about a {@see Relation}</li>
68 * </ul>
69 *
70 * @see HistoryBrowser
71 */
72public class HistoryBrowserModel extends Observable implements LayerChangeListener, DataSetListener {
73 /** the history of an OsmPrimitive */
74 private History history;
75 private HistoryOsmPrimitive reference;
76 private HistoryOsmPrimitive current;
77 /**
78 * latest isn't a reference of history. It's a clone of the currently edited
79 * {@see OsmPrimitive} in the current edit layer.
80 */
81 private HistoryOsmPrimitive latest;
82
83 private VersionTableModel versionTableModel;
84 private TagTableModel currentTagTableModel;
85 private TagTableModel referenceTagTableModel;
86 private RelationMemberTableModel currentRelationMemberTableModel;
87 private RelationMemberTableModel referenceRelationMemberTableModel;
88 private DiffTableModel referenceNodeListTableModel;
89 private DiffTableModel currentNodeListTableModel;
90
91 /**
92 * constructor
93 */
94 public HistoryBrowserModel() {
95 versionTableModel = new VersionTableModel();
96 currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
97 referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
98 referenceNodeListTableModel = new DiffTableModel();
99 currentNodeListTableModel = new DiffTableModel();
100 currentRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
101 referenceRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
102
103 if (getEditLayer() != null) {
104 getEditLayer().data.addDataSetListener(this);
105 }
106 MapView.addLayerChangeListener(this);
107 }
108
109 /**
110 * Creates a new history browser model for a given history.
111 *
112 * @param history the history. Must not be null.
113 * @throws IllegalArgumentException thrown if history is null
114 */
115 public HistoryBrowserModel(History history) {
116 this();
117 CheckParameterUtil.ensureParameterNotNull(history, "history");
118 setHistory(history);
119 }
120
121 /**
122 * Replies the current edit layer; null, if there isn't a current edit layer
123 * of type {@see OsmDataLayer}.
124 *
125 * @return the current edit layer
126 */
127 protected OsmDataLayer getEditLayer() {
128 try {
129 return Main.map.mapView.getEditLayer();
130 } catch(NullPointerException e) {
131 return null;
132 }
133 }
134
135 /**
136 * replies the history managed by this model
137 * @return the history
138 */
139 public History getHistory() {
140 return history;
141 }
142
143 protected boolean hasNewNodes(Way way) {
144 for (Node n: way.getNodes()) {
145 if (n.isNew()) return true;
146 }
147 return false;
148 }
149 protected boolean canShowAsLatest(OsmPrimitive primitive) {
150 if (primitive == null) return false;
151 if (primitive.isNew() || !primitive.isUsable()) return false;
152
153 //try creating a history primitive. if that fails, the primitive cannot be used.
154 try {
155 HistoryOsmPrimitive.forOsmPrimitive(primitive);
156 } catch (Exception ign) {
157 return false;
158 }
159
160 if (history == null) return false;
161 // only show latest of the same version if it is modified
162 if (history.getByVersion(primitive.getVersion()) != null)
163 return primitive.isModified();
164
165 // latest has a higher version than one of the primitives
166 // in the history (probably because the history got out of sync
167 // with uploaded data) -> show the primitive as latest
168 return true;
169 }
170
171 /**
172 * sets the history to be managed by this model
173 *
174 * @param history the history
175 *
176 */
177 public void setHistory(History history) {
178 this.history = history;
179 if (history.getNumVersions() > 0) {
180 HistoryOsmPrimitive newLatest = null;
181 if (getEditLayer() != null) {
182 OsmPrimitive p = getEditLayer().data.getPrimitiveById(history.getId(), history.getType());
183 if (canShowAsLatest(p)) {
184 newLatest = new HistoryPrimitiveBuilder().build(p);
185 }
186 }
187 if (newLatest == null) {
188 current = history.getLatest();
189 int prevIndex = history.getNumVersions() - 2;
190 reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex);
191 } else {
192 reference = history.getLatest();
193 current = newLatest;
194 }
195 setLatest(newLatest);
196 }
197 initTagTableModels();
198 fireModelChange();
199 }
200
201 protected void fireModelChange() {
202 initNodeListTableModels();
203 setChanged();
204 notifyObservers();
205 versionTableModel.fireTableDataChanged();
206 }
207
208 /**
209 * Replies the table model to be used in a {@see JTable} which
210 * shows the list of versions in this history.
211 *
212 * @return the table model
213 */
214 public VersionTableModel getVersionTableModel() {
215 return versionTableModel;
216 }
217
218 protected void initTagTableModels() {
219 currentTagTableModel.initKeyList();
220 referenceTagTableModel.initKeyList();
221 }
222
223 /**
224 * Should be called everytime either reference of current changes to update the diff.
225 * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels
226 */
227 protected void initNodeListTableModels() {
228
229 if(current.getType() != OsmPrimitiveType.WAY || reference.getType() != OsmPrimitiveType.WAY)
230 return;
231 TwoColumnDiff diff = new TwoColumnDiff(
232 ((HistoryWay)reference).getNodes().toArray(),
233 ((HistoryWay)current).getNodes().toArray());
234 referenceNodeListTableModel.setRows(diff.referenceDiff);
235 currentNodeListTableModel.setRows(diff.currentDiff);
236
237 referenceNodeListTableModel.fireTableDataChanged();
238 currentNodeListTableModel.fireTableDataChanged();
239 }
240
241 protected void initMemberListTableModels() {
242 currentRelationMemberTableModel.fireTableDataChanged();
243 referenceRelationMemberTableModel.fireTableDataChanged();
244 }
245
246 /**
247 * replies the tag table model for the respective point in time
248 *
249 * @param pointInTimeType the type of the point in time (must not be null)
250 * @return the tag table model
251 * @exception IllegalArgumentException thrown, if pointInTimeType is null
252 */
253 public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
254 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
255 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
256 return currentTagTableModel;
257 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
258 return referenceTagTableModel;
259
260 // should not happen
261 return null;
262 }
263
264 public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
265 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
266 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
267 return currentNodeListTableModel;
268 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
269 return referenceNodeListTableModel;
270
271 // should not happen
272 return null;
273 }
274
275 public RelationMemberTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
276 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
277 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
278 return currentRelationMemberTableModel;
279 else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
280 return referenceRelationMemberTableModel;
281
282 // should not happen
283 return null;
284 }
285
286 /**
287 * Sets the {@see HistoryOsmPrimitive} which plays the role of a reference point
288 * in time (see {@see PointInTimeType}).
289 *
290 * @param reference the reference history primitive. Must not be null.
291 * @throws IllegalArgumentException thrown if reference is null
292 * @throws IllegalStateException thrown if this model isn't a assigned a history yet
293 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode
294 *
295 * @see #setHistory(History)
296 * @see PointInTimeType
297 */
298 public void setReferencePointInTime(HistoryOsmPrimitive reference) throws IllegalArgumentException, IllegalStateException{
299 CheckParameterUtil.ensureParameterNotNull(reference, "reference");
300 if (history == null)
301 throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive."));
302 if (reference.getId() != history.getId())
303 throw new IllegalArgumentException(tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(), history.getId()));
304 HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion());
305 if (primitive == null)
306 throw new IllegalArgumentException(tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion()));
307
308 this.reference = reference;
309 initTagTableModels();
310 initNodeListTableModels();
311 initMemberListTableModels();
312 setChanged();
313 notifyObservers();
314 }
315
316 /**
317 * Sets the {@see HistoryOsmPrimitive} which plays the role of the current point
318 * in time (see {@see PointInTimeType}).
319 *
320 * @param reference the reference history primitive. Must not be null.
321 * @throws IllegalArgumentException thrown if reference is null
322 * @throws IllegalStateException thrown if this model isn't a assigned a history yet
323 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode
324 *
325 * @see #setHistory(History)
326 * @see PointInTimeType
327 */
328 public void setCurrentPointInTime(HistoryOsmPrimitive current) throws IllegalArgumentException, IllegalStateException{
329 CheckParameterUtil.ensureParameterNotNull(current, "current");
330 if (history == null)
331 throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive."));
332 if (current.getId() != history.getId())
333 throw new IllegalArgumentException(tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(), history.getId()));
334 HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion());
335 if (primitive == null)
336 throw new IllegalArgumentException(tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion()));
337 this.current = current;
338 initTagTableModels();
339 initNodeListTableModels();
340 initMemberListTableModels();
341 setChanged();
342 notifyObservers();
343 }
344
345 /**
346 * Replies the history OSM primitive for the {@see PointInTimeType#CURRENT_POINT_IN_TIME}
347 *
348 * @return the history OSM primitive for the {@see PointInTimeType#CURRENT_POINT_IN_TIME} (may be null)
349 */
350 public HistoryOsmPrimitive getCurrentPointInTime() {
351 return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME);
352 }
353
354 /**
355 * Replies the history OSM primitive for the {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
356 *
357 * @return the history OSM primitive for the {@see PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null)
358 */
359 public HistoryOsmPrimitive getReferencePointInTime() {
360 return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME);
361 }
362
363 /**
364 * replies the history OSM primitive for a given point in time
365 *
366 * @param type the type of the point in time (must not be null)
367 * @return the respective primitive. Can be null.
368 * @exception IllegalArgumentException thrown, if type is null
369 */
370 public HistoryOsmPrimitive getPointInTime(PointInTimeType type) throws IllegalArgumentException {
371 CheckParameterUtil.ensureParameterNotNull(type, "type");
372 if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
373 return current;
374 else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
375 return reference;
376
377 // should not happen
378 return null;
379 }
380
381 /**
382 * Returns true if <code>primitive</code> is the latest primitive
383 * representing the version currently edited in the current data
384 * layer.
385 *
386 * @param primitive the primitive to check
387 * @return true if <code>primitive</code> is the latest primitive
388 */
389 public boolean isLatest(HistoryOsmPrimitive primitive) {
390 if (primitive == null) return false;
391 return primitive == latest;
392 }
393
394 /**
395 * The table model for the list of versions in the current history
396 *
397 */
398 public class VersionTableModel extends AbstractTableModel {
399
400 private VersionTableModel() {
401 }
402
403 @Override
404 public int getRowCount() {
405 if (history == null)
406 return 0;
407 int ret = history.getNumVersions();
408 if (latest != null) {
409 ret++;
410 }
411 return ret;
412 }
413
414 @Override
415 public Object getValueAt(int row, int column) {
416 switch (column) {
417 case 0:
418 return Long.toString(getPrimitive(row).getVersion());
419 case 1:
420 return isReferencePointInTime(row);
421 case 2:
422 return isCurrentPointInTime(row);
423 case 3: {
424 User user = getPrimitive(row).getUser();
425 int status;
426 if (user == null) {
427 status = User.STATUS_UNKNOWN;
428 } else {
429 status = user.getRelicensingStatus();
430 }
431 return UserListDialog.getRelicensingStatusIcon(status);
432 }
433 case 4: {
434 HistoryOsmPrimitive p = getPrimitive(row);
435 if (p != null)
436 return new SimpleDateFormat().format(p.getTimestamp());
437 return null;
438 }
439 case 5: {
440 HistoryOsmPrimitive p = getPrimitive(row);
441 if (p != null) {
442 User user = p.getUser();
443 if (user != null)
444 return "<html>" + XmlWriter.encode(user.getName()) + " <font color=gray>(" + user.getId() + ")</font></html>";
445 }
446 return null;
447 }
448 }
449 return null;
450 }
451
452 @Override
453 public void setValueAt(Object aValue, int row, int column) {
454 if (!((Boolean) aValue)) return;
455 switch (column) {
456 case 1:
457 setReferencePointInTime(row);
458 break;
459 case 2:
460 setCurrentPointInTime(row);
461 break;
462 default:
463 return;
464 }
465 fireTableDataChanged();
466 }
467
468 @Override
469 public boolean isCellEditable(int row, int column) {
470 return column >= 1 && column <= 2;
471 }
472
473 public void setReferencePointInTime(int row) {
474 if (history == null) return;
475 if (row == history.getNumVersions()) {
476 if (latest != null) {
477 HistoryBrowserModel.this.setReferencePointInTime(latest);
478 }
479 return;
480 }
481 if (row < 0 || row > history.getNumVersions()) return;
482 HistoryOsmPrimitive reference = history.get(row);
483 HistoryBrowserModel.this.setReferencePointInTime(reference);
484 }
485
486 public void setCurrentPointInTime(int row) {
487 if (history == null) return;
488 if (row == history.getNumVersions()) {
489 if (latest != null) {
490 HistoryBrowserModel.this.setCurrentPointInTime(latest);
491 }
492 return;
493 }
494 if (row < 0 || row > history.getNumVersions()) return;
495 HistoryOsmPrimitive current = history.get(row);
496 HistoryBrowserModel.this.setCurrentPointInTime(current);
497 }
498
499 public boolean isReferencePointInTime(int row) {
500 if (history == null) return false;
501 if (row == history.getNumVersions())
502 return latest == reference;
503 if (row < 0 || row > history.getNumVersions()) return false;
504 HistoryOsmPrimitive p = history.get(row);
505 return p == reference;
506 }
507
508 public boolean isCurrentPointInTime(int row) {
509 if (history == null) return false;
510 if (row == history.getNumVersions())
511 return latest == current;
512 if (row < 0 || row > history.getNumVersions()) return false;
513 HistoryOsmPrimitive p = history.get(row);
514 return p == current;
515 }
516
517 public HistoryOsmPrimitive getPrimitive(int row) {
518 if (history == null)
519 return null;
520 return isLatest(row) ? latest : history.get(row);
521 }
522
523 public boolean isLatest(int row) {
524 return row >= history.getNumVersions();
525 }
526
527 public OsmPrimitive getLatest() {
528 if (latest == null) return null;
529 if (getEditLayer() == null) return null;
530 OsmPrimitive p = getEditLayer().data.getPrimitiveById(latest.getId(), latest.getType());
531 return p;
532 }
533
534 @Override
535 public int getColumnCount() {
536 return 6;
537 }
538 }
539
540 /**
541 * The table model for the tags of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
542 * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
543 *
544 */
545 public class TagTableModel extends AbstractTableModel {
546
547 private ArrayList<String> keys;
548 private PointInTimeType pointInTimeType;
549
550 protected void initKeyList() {
551 HashSet<String> keySet = new HashSet<String>();
552 if (current != null) {
553 keySet.addAll(current.getTags().keySet());
554 }
555 if (reference != null) {
556 keySet.addAll(reference.getTags().keySet());
557 }
558 keys = new ArrayList<String>(keySet);
559 Collections.sort(keys);
560 fireTableDataChanged();
561 }
562
563 protected TagTableModel(PointInTimeType type) {
564 pointInTimeType = type;
565 initKeyList();
566 }
567
568 @Override
569 public int getRowCount() {
570 if (keys == null) return 0;
571 return keys.size();
572 }
573
574 @Override
575 public Object getValueAt(int row, int column) {
576 return keys.get(row);
577 }
578
579 @Override
580 public boolean isCellEditable(int row, int column) {
581 return false;
582 }
583
584 public boolean hasTag(String key) {
585 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
586 if (primitive == null)
587 return false;
588 return primitive.hasTag(key);
589 }
590
591 public String getValue(String key) {
592 HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
593 if (primitive == null)
594 return null;
595 return primitive.get(key);
596 }
597
598 public boolean oppositeHasTag(String key) {
599 PointInTimeType opposite = pointInTimeType.opposite();
600 HistoryOsmPrimitive primitive = getPointInTime(opposite);
601 if (primitive == null)
602 return false;
603 return primitive.hasTag(key);
604 }
605
606 public String getOppositeValue(String key) {
607 PointInTimeType opposite = pointInTimeType.opposite();
608 HistoryOsmPrimitive primitive = getPointInTime(opposite);
609 if (primitive == null)
610 return null;
611 return primitive.get(key);
612 }
613
614 public boolean hasSameValueAsOpposite(String key) {
615 String value = getValue(key);
616 String oppositeValue = getOppositeValue(key);
617 if (value == null || oppositeValue == null)
618 return false;
619 return value.equals(oppositeValue);
620 }
621
622 public PointInTimeType getPointInTimeType() {
623 return pointInTimeType;
624 }
625
626 public boolean isCurrentPointInTime() {
627 return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME);
628 }
629
630 public boolean isReferencePointInTime() {
631 return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
632 }
633
634 @Override
635 public int getColumnCount() {
636 return 1;
637 }
638 }
639
640 /**
641 * The table model for the relation members of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
642 * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
643 *
644 */
645
646 public class RelationMemberTableModel extends AbstractTableModel {
647
648 private PointInTimeType pointInTimeType;
649
650 private RelationMemberTableModel(PointInTimeType pointInTimeType) {
651 this.pointInTimeType = pointInTimeType;
652 }
653
654 @Override
655 public int getRowCount() {
656 // Match the size of the opposite table so comparison is less confusing.
657 // (scroll bars lines up properly, etc.)
658 int n = 0;
659 if (current != null && current.getType().equals(OsmPrimitiveType.RELATION)) {
660 n = ((HistoryRelation)current).getNumMembers();
661 }
662 if (reference != null && reference.getType().equals(OsmPrimitiveType.RELATION)) {
663 n = Math.max(n,((HistoryRelation)reference).getNumMembers());
664 }
665 return n;
666 }
667
668 protected HistoryRelation getRelation() {
669 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
670 if (! current.getType().equals(OsmPrimitiveType.RELATION))
671 return null;
672 return (HistoryRelation)current;
673 }
674 if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
675 if (! reference.getType().equals(OsmPrimitiveType.RELATION))
676 return null;
677 return (HistoryRelation)reference;
678 }
679
680 // should not happen
681 return null;
682 }
683
684 protected HistoryRelation getOppositeRelation() {
685 PointInTimeType opposite = pointInTimeType.opposite();
686 if (opposite.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
687 if (! current.getType().equals(OsmPrimitiveType.RELATION))
688 return null;
689 return (HistoryRelation)current;
690 }
691 if (opposite.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
692 if (! reference.getType().equals(OsmPrimitiveType.RELATION))
693 return null;
694 return (HistoryRelation)reference;
695 }
696
697 // should not happen
698 return null;
699 }
700
701 @Override
702 public Object getValueAt(int row, int column) {
703 HistoryRelation relation = getRelation();
704 if (relation == null)
705 return null;
706 if (row >= relation.getNumMembers()) // see getRowCount
707 return null;
708 return relation.getMembers().get(row);
709 }
710
711 @Override
712 public boolean isCellEditable(int row, int column) {
713 return false;
714 }
715
716 public boolean isSameInOppositeWay(int row) {
717 HistoryRelation thisRelation = getRelation();
718 HistoryRelation oppositeRelation = getOppositeRelation();
719 if (thisRelation == null || oppositeRelation == null)
720 return false;
721 if (row >= oppositeRelation.getNumMembers())
722 return false;
723 return
724 thisRelation.getMembers().get(row).getPrimitiveId() == oppositeRelation.getMembers().get(row).getPrimitiveId()
725 && thisRelation.getMembers().get(row).getRole().equals(oppositeRelation.getMembers().get(row).getRole());
726 }
727
728 public boolean isInOppositeWay(int row) {
729 HistoryRelation thisRelation = getRelation();
730 HistoryRelation oppositeRelation = getOppositeRelation();
731 if (thisRelation == null || oppositeRelation == null)
732 return false;
733 return oppositeRelation.getMembers().contains(thisRelation.getMembers().get(row));
734 }
735
736 @Override
737 public int getColumnCount() {
738 return 1;
739 }
740 }
741
742 protected void setLatest(HistoryOsmPrimitive latest) {
743 if (latest == null) {
744 if (this.current == this.latest) {
745 this.current = history.getLatest();
746 }
747 if (this.reference == this.latest) {
748 this.current = history.getLatest();
749 }
750 this.latest = null;
751 } else {
752 if (this.current == this.latest) {
753 this.current = latest;
754 }
755 if (this.reference == this.latest) {
756 this.reference = latest;
757 }
758 this.latest = latest;
759 }
760 fireModelChange();
761 }
762
763 /**
764 * Removes this model as listener for data change and layer change
765 * events.
766 *
767 */
768 public void unlinkAsListener() {
769 if (getEditLayer() != null) {
770 getEditLayer().data.removeDataSetListener(this);
771 }
772 MapView.removeLayerChangeListener(this);
773 }
774
775 /* ---------------------------------------------------------------------- */
776 /* DataSetListener */
777 /* ---------------------------------------------------------------------- */
778 public void nodeMoved(NodeMovedEvent event) {
779 Node node = event.getNode();
780 if (!node.isNew() && node.getId() == history.getId()) {
781 setLatest(new HistoryPrimitiveBuilder().build(node));
782 }
783 }
784
785 public void primitivesAdded(PrimitivesAddedEvent event) {
786 for (OsmPrimitive p: event.getPrimitives()) {
787 if (canShowAsLatest(p)) {
788 setLatest(new HistoryPrimitiveBuilder().build(p));
789 }
790 }
791 }
792
793 public void primitivesRemoved(PrimitivesRemovedEvent event) {
794 for (OsmPrimitive p: event.getPrimitives()) {
795 if (!p.isNew() && p.getId() == history.getId()) {
796 setLatest(null);
797 }
798 }
799 }
800
801 public void relationMembersChanged(RelationMembersChangedEvent event) {
802 Relation r = event.getRelation();
803 if (!r.isNew() && r.getId() == history.getId()) {
804 setLatest(new HistoryPrimitiveBuilder().build(r));
805 }
806 }
807
808 public void tagsChanged(TagsChangedEvent event) {
809 OsmPrimitive prim = event.getPrimitive();
810 if (!prim.isNew() && prim.getId() == history.getId()) {
811 setLatest(new HistoryPrimitiveBuilder().build(prim));
812 }
813 }
814
815 public void wayNodesChanged(WayNodesChangedEvent event) {
816 Way way = event.getChangedWay();
817 if (!way.isNew() && way.getId() == history.getId()) {
818 setLatest(new HistoryPrimitiveBuilder().build(way));
819 }
820 }
821
822 public void dataChanged(DataChangedEvent event) {
823 OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType());
824 HistoryOsmPrimitive latest;
825 if (canShowAsLatest(primitive)) {
826 latest = new HistoryPrimitiveBuilder().build(primitive);
827 } else {
828 latest = null;
829 }
830 setLatest(latest);
831 fireModelChange();
832 }
833
834 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
835 // Irrelevant
836 }
837
838 /* ---------------------------------------------------------------------- */
839 /* LayerChangeListener */
840 /* ---------------------------------------------------------------------- */
841 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
842 if (oldLayer != null && oldLayer instanceof OsmDataLayer) {
843 OsmDataLayer l = (OsmDataLayer)oldLayer;
844 l.data.removeDataSetListener(this);
845 }
846 if (newLayer == null || ! (newLayer instanceof OsmDataLayer)) {
847 latest = null;
848 fireModelChange();
849 return;
850 }
851 OsmDataLayer l = (OsmDataLayer)newLayer;
852 l.data.addDataSetListener(this);
853 OsmPrimitive primitive = l.data.getPrimitiveById(history.getId(), history.getType());
854 HistoryOsmPrimitive latest;
855 if (canShowAsLatest(primitive)) {
856 latest = new HistoryPrimitiveBuilder().build(primitive);
857 } else {
858 latest = null;
859 }
860 setLatest(latest);
861 fireModelChange();
862 }
863
864 public void layerAdded(Layer newLayer) {}
865 public void layerRemoved(Layer oldLayer) {}
866
867 /**
868 * Creates a {@see HistoryOsmPrimitive} from a {@see OsmPrimitive}
869 *
870 */
871 static class HistoryPrimitiveBuilder extends AbstractVisitor {
872 private HistoryOsmPrimitive clone;
873
874 public void visit(Node n) {
875 clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), n.getUser(), 0, n.getTimestamp(), n.getCoor());
876 clone.setTags(n.getKeys());
877 }
878
879 public void visit(Relation r) {
880 clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), r.getUser(), 0, r.getTimestamp());
881 clone.setTags(r.getKeys());
882 HistoryRelation hr = (HistoryRelation)clone;
883 for (RelationMember rm : r.getMembers()) {
884 hr.addMember(new org.openstreetmap.josm.data.osm.history.RelationMember(rm.getRole(), rm.getType(), rm.getUniqueId()));
885 }
886 }
887
888 public void visit(Way w) {
889 clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), w.getUser(), 0, w.getTimestamp());
890 clone.setTags(w.getKeys());
891 for (Node n: w.getNodes()) {
892 ((HistoryWay)clone).addNode(n.getUniqueId());
893 }
894 }
895
896 public HistoryOsmPrimitive build(OsmPrimitive primitive) {
897 primitive.visit(this);
898 return clone;
899 }
900 }
901}
902
903/**
904 * Simple model storing "diff cells" in a list. Could probably have used a DefaultTableModel instead..
905 *
906 * {@see NodeListDiffTableCellRenderer}
907 */
908class DiffTableModel extends AbstractTableModel {
909 private List<TwoColumnDiff.Item> rows;
910
911 public void setRows(List<TwoColumnDiff.Item> rows) {
912 this.rows = rows;
913 }
914
915 public DiffTableModel(List<TwoColumnDiff.Item> rows) {
916 this.rows = rows;
917 }
918 public DiffTableModel() {
919 this.rows = new ArrayList<TwoColumnDiff.Item>();
920 }
921 @Override
922 public int getRowCount() {
923 return rows.size();
924 }
925
926 @Override
927 public int getColumnCount() {
928 return 1;
929 }
930
931 @Override
932 public TwoColumnDiff.Item getValueAt(int rowIndex, int columnIndex) {
933 return rows.get(rowIndex);
934 }
935}
936
937
938/// Feel free to move me somewhere else. Maybe a bit specific for josm.tools?
939/**
940 * Produces a "two column diff" of two lists. (same as diff -y)
941 *
942 * Each list is annotated with the changes relative to the other, and "empty" cells are inserted so the lists are comparable item by item.
943 *
944 * diff on [1 2 3 4] [1 a 4 5] yields:
945 *
946 * item(SAME, 1) item(SAME, 1)
947 * item(CHANGED, 2) item(CHANGED, 2)
948 * item(DELETED, 3) item(EMPTY)
949 * item(SAME, 4) item(SAME, 4)
950 * item(EMPTY) item(INSERTED, 5)
951 *
952 * @author olejorgenb
953 */
954class TwoColumnDiff {
955 public static class Item {
956 public static final int INSERTED = 1;
957 public static final int DELETED = 2;
958 public static final int CHANGED = 3;
959 public static final int SAME = 4;
960 public static final int EMPTY = 5; // value should be null
961 public Item(int state, Object value) {
962 this.state = state;
963 this.value = state == EMPTY ? null : value;
964 }
965
966 public final Object value;
967 public final int state;
968 }
969
970 public ArrayList<Item> referenceDiff;
971 public ArrayList<Item> currentDiff;
972 Object[] reference;
973 Object[] current;
974
975 /**
976 * The arguments will _not_ be modified
977 */
978 public TwoColumnDiff(Object[] reference, Object[] current) {
979 this.reference = reference;
980 this.current = current;
981 referenceDiff = new ArrayList<Item>();
982 currentDiff = new ArrayList<Item>();
983 diff();
984 }
985 private void diff() {
986 Diff diff = new Diff(reference, current);
987 Diff.change script = diff.diff_2(false);
988 twoColumnDiffFromScript(script, reference, current);
989 }
990
991 /**
992 * The result from the diff algorithm is a "script" (a compressed description of the changes)
993 * This method expands this script into a full two column description.
994 */
995 private void twoColumnDiffFromScript(Diff.change script, Object[] a, Object[] b) {
996 int ia = 0;
997 int ib = 0;
998
999 while(script != null) {
1000 int deleted = script.deleted;
1001 int inserted = script.inserted;
1002 while(ia < script.line0 && ib < script.line1){
1003 // System.out.println(" "+a[ia] + "\t "+b[ib]);
1004 Item cell = new Item(Item.SAME, a[ia]);
1005 referenceDiff.add(cell);
1006 currentDiff.add(cell);
1007 ia++;
1008 ib++;
1009 }
1010
1011 while(inserted > 0 || deleted > 0) {
1012 if(inserted > 0 && deleted > 0) {
1013 // System.out.println("="+a[ia] + "\t="+b[ib]);
1014 referenceDiff.add(new Item(Item.CHANGED, a[ia++]));
1015 currentDiff.add(new Item(Item.CHANGED, b[ib++]));
1016 } else if(inserted > 0) {
1017 // System.out.println("\t+" + b[ib]);
1018 referenceDiff.add(new Item(Item.EMPTY, null));
1019 currentDiff.add(new Item(Item.INSERTED, b[ib++]));
1020 } else if(deleted > 0) {
1021 // System.out.println("-"+a[ia]);
1022 referenceDiff.add(new Item(Item.DELETED, a[ia++]));
1023 currentDiff.add(new Item(Item.EMPTY, null));
1024 }
1025 inserted--;
1026 deleted--;
1027 }
1028 script = script.link;
1029 }
1030 while(ia < a.length && ib < b.length) {
1031 // System.out.println((ia < a.length ? " "+a[ia]+"\t" : "\t") + (ib < b.length ? " "+b[ib] : ""));
1032 referenceDiff.add(new Item(Item.SAME, a[ia++]));
1033 currentDiff.add(new Item(Item.SAME, b[ib++]));
1034 }
1035 }
1036}
Note: See TracBrowser for help on using the repository browser.