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

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

history browser: changed selection of 2 compared version. WAS: double click and single click. NOW: 2 rows of radio buttons (inspired by Mediawiki)

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