source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/ListMergeModel.java@ 8375

Last change on this file since 8375 was 8375, checked in by Don-vip, 9 years ago

code style - Method stores return result in local before immediately returning it

  • Property svn:eol-style set to native
File size: 30.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.pair;
3
4import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_MERGED;
5import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_THEIR;
6import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.THEIR_WITH_MERGED;
7import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MERGED_ENTRIES;
8import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MY_ENTRIES;
9import static org.openstreetmap.josm.gui.conflict.pair.ListRole.THEIR_ENTRIES;
10import static org.openstreetmap.josm.tools.I18n.tr;
11
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.ArrayList;
15import java.util.HashMap;
16import java.util.List;
17import java.util.Map;
18import java.util.Observable;
19
20import javax.swing.AbstractListModel;
21import javax.swing.ComboBoxModel;
22import javax.swing.DefaultListSelectionModel;
23import javax.swing.JOptionPane;
24import javax.swing.JTable;
25import javax.swing.ListSelectionModel;
26import javax.swing.table.DefaultTableModel;
27import javax.swing.table.TableModel;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.data.osm.DataSet;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.PrimitiveId;
33import org.openstreetmap.josm.data.osm.RelationMember;
34import org.openstreetmap.josm.gui.HelpAwareOptionPane;
35import org.openstreetmap.josm.gui.help.HelpUtil;
36import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
37import org.openstreetmap.josm.tools.CheckParameterUtil;
38import org.openstreetmap.josm.tools.Utils;
39
40/**
41 * ListMergeModel is a model for interactively comparing and merging two list of entries
42 * of type T. It maintains three lists of entries of type T:
43 * <ol>
44 * <li>the list of <em>my</em> entries</li>
45 * <li>the list of <em>their</em> entries</li>
46 * <li>the list of <em>merged</em> entries</li>
47 * </ol>
48 *
49 * A ListMergeModel is a factory for three {@link TableModel}s and three {@link ListSelectionModel}s:
50 * <ol>
51 * <li>the table model and the list selection for for a {@link JTable} which shows my entries.
52 * See {@link #getMyTableModel()} and {@link ListMergeModel#getMySelectionModel()}</li>
53 * <li>dito for their entries and merged entries</li>
54 * </ol>
55 *
56 * A ListMergeModel can be ''frozen''. If it's frozen, it doesn't accept additional merge
57 * decisions. {@link PropertyChangeListener}s can register for property value changes of
58 * {@link #FROZEN_PROP}.
59 *
60 * ListMergeModel is an abstract class. Three methods have to be implemented by subclasses:
61 * <ul>
62 * <li>{@link ListMergeModel#cloneEntryForMergedList} - clones an entry of type T</li>
63 * <li>{@link ListMergeModel#isEqualEntry} - checks whether two entries are equals </li>
64 * <li>{@link ListMergeModel#setValueAt(DefaultTableModel, Object, int, int)} - handles values edited in
65 * a JTable, dispatched from {@link TableModel#setValueAt(Object, int, int)} </li>
66 * </ul>
67 * A ListMergeModel is used in combination with a {@link ListMerger}.
68 *
69 * @param <T> the type of the list entries
70 * @see ListMerger
71 */
72public abstract class ListMergeModel<T extends PrimitiveId> extends Observable {
73 public static final String FROZEN_PROP = ListMergeModel.class.getName() + ".frozen";
74
75 private static final int MAX_DELETED_PRIMITIVE_IN_DIALOG = 5;
76
77 protected Map<ListRole, ArrayList<T>> entries;
78
79 protected EntriesTableModel myEntriesTableModel;
80 protected EntriesTableModel theirEntriesTableModel;
81 protected EntriesTableModel mergedEntriesTableModel;
82
83 protected EntriesSelectionModel myEntriesSelectionModel;
84 protected EntriesSelectionModel theirEntriesSelectionModel;
85 protected EntriesSelectionModel mergedEntriesSelectionModel;
86
87 private final List<PropertyChangeListener> listeners;
88 private boolean isFrozen = false;
89 private final ComparePairListModel comparePairListModel;
90
91 private DataSet myDataset;
92 private Map<PrimitiveId, PrimitiveId> mergedMap;
93
94 /**
95 * Creates a clone of an entry of type T suitable to be included in the
96 * list of merged entries
97 *
98 * @param entry the entry
99 * @return the cloned entry
100 */
101 protected abstract T cloneEntryForMergedList(T entry);
102
103 /**
104 * checks whether two entries are equal. This is not necessarily the same as
105 * e1.equals(e2).
106 *
107 * @param e1 the first entry
108 * @param e2 the second entry
109 * @return true, if the entries are equal, false otherwise.
110 */
111 public abstract boolean isEqualEntry(T e1, T e2);
112
113 /**
114 * Handles method dispatches from {@link TableModel#setValueAt(Object, int, int)}.
115 *
116 * @param model the table model
117 * @param value the value to be set
118 * @param row the row index
119 * @param col the column index
120 *
121 * @see TableModel#setValueAt(Object, int, int)
122 */
123 protected abstract void setValueAt(DefaultTableModel model, Object value, int row, int col);
124
125 /**
126 *
127 * @param entry
128 * @return Primitive from my dataset referenced by entry
129 */
130 public OsmPrimitive getMyPrimitive(T entry) {
131 return getMyPrimitiveById(entry);
132 }
133
134 public final OsmPrimitive getMyPrimitiveById(PrimitiveId entry) {
135 OsmPrimitive result = myDataset.getPrimitiveById(entry);
136 if (result == null && mergedMap != null) {
137 PrimitiveId id = mergedMap.get(entry);
138 if (id == null && entry instanceof OsmPrimitive) {
139 id = mergedMap.get(((OsmPrimitive)entry).getPrimitiveId());
140 }
141 if (id != null) {
142 result = myDataset.getPrimitiveById(id);
143 }
144 }
145 return result;
146 }
147
148 protected void buildMyEntriesTableModel() {
149 myEntriesTableModel = new EntriesTableModel(MY_ENTRIES);
150 }
151
152 protected void buildTheirEntriesTableModel() {
153 theirEntriesTableModel = new EntriesTableModel(THEIR_ENTRIES);
154 }
155
156 protected void buildMergedEntriesTableModel() {
157 mergedEntriesTableModel = new EntriesTableModel(MERGED_ENTRIES);
158 }
159
160 protected List<T> getMergedEntries() {
161 return entries.get(MERGED_ENTRIES);
162 }
163
164 protected List<T> getMyEntries() {
165 return entries.get(MY_ENTRIES);
166 }
167
168 protected List<T> getTheirEntries() {
169 return entries.get(THEIR_ENTRIES);
170 }
171
172 public int getMyEntriesSize() {
173 return getMyEntries().size();
174 }
175
176 public int getMergedEntriesSize() {
177 return getMergedEntries().size();
178 }
179
180 public int getTheirEntriesSize() {
181 return getTheirEntries().size();
182 }
183
184 public ListMergeModel() {
185 entries = new HashMap<>();
186 for (ListRole role : ListRole.values()) {
187 entries.put(role, new ArrayList<T>());
188 }
189
190 buildMyEntriesTableModel();
191 buildTheirEntriesTableModel();
192 buildMergedEntriesTableModel();
193
194 myEntriesSelectionModel = new EntriesSelectionModel(entries.get(MY_ENTRIES));
195 theirEntriesSelectionModel = new EntriesSelectionModel(entries.get(THEIR_ENTRIES));
196 mergedEntriesSelectionModel = new EntriesSelectionModel(entries.get(MERGED_ENTRIES));
197
198 listeners = new ArrayList<>();
199 comparePairListModel = new ComparePairListModel();
200
201 setFrozen(true);
202 }
203
204 public void addPropertyChangeListener(PropertyChangeListener listener) {
205 synchronized(listeners) {
206 if (listener != null && ! listeners.contains(listener)) {
207 listeners.add(listener);
208 }
209 }
210 }
211
212 public void removePropertyChangeListener(PropertyChangeListener listener) {
213 synchronized(listeners) {
214 if (listener != null && listeners.contains(listener)) {
215 listeners.remove(listener);
216 }
217 }
218 }
219
220 protected void fireFrozenChanged(boolean oldValue, boolean newValue) {
221 synchronized(listeners) {
222 PropertyChangeEvent evt = new PropertyChangeEvent(this, FROZEN_PROP, oldValue, newValue);
223 for (PropertyChangeListener listener: listeners) {
224 listener.propertyChange(evt);
225 }
226 }
227 }
228
229 public final void setFrozen(boolean isFrozen) {
230 boolean oldValue = this.isFrozen;
231 this.isFrozen = isFrozen;
232 fireFrozenChanged(oldValue, this.isFrozen);
233 }
234
235 public final boolean isFrozen() {
236 return isFrozen;
237 }
238
239 public OsmPrimitivesTableModel getMyTableModel() {
240 return myEntriesTableModel;
241 }
242
243 public OsmPrimitivesTableModel getTheirTableModel() {
244 return theirEntriesTableModel;
245 }
246
247 public OsmPrimitivesTableModel getMergedTableModel() {
248 return mergedEntriesTableModel;
249 }
250
251 public EntriesSelectionModel getMySelectionModel() {
252 return myEntriesSelectionModel;
253 }
254
255 public EntriesSelectionModel getTheirSelectionModel() {
256 return theirEntriesSelectionModel;
257 }
258
259 public EntriesSelectionModel getMergedSelectionModel() {
260 return mergedEntriesSelectionModel;
261 }
262
263 protected void fireModelDataChanged() {
264 myEntriesTableModel.fireTableDataChanged();
265 theirEntriesTableModel.fireTableDataChanged();
266 mergedEntriesTableModel.fireTableDataChanged();
267 setChanged();
268 notifyObservers();
269 }
270
271 protected void copyToTop(ListRole role, int []rows) {
272 copy(role, rows, 0);
273 mergedEntriesSelectionModel.setSelectionInterval(0, rows.length -1);
274 }
275
276 /**
277 * Copies the nodes given by indices in rows from the list of my nodes to the
278 * list of merged nodes. Inserts the nodes at the top of the list of merged
279 * nodes.
280 *
281 * @param rows the indices
282 */
283 public void copyMyToTop(int [] rows) {
284 copyToTop(MY_ENTRIES, rows);
285 }
286
287 /**
288 * Copies the nodes given by indices in rows from the list of their nodes to the
289 * list of merged nodes. Inserts the nodes at the top of the list of merged
290 * nodes.
291 *
292 * @param rows the indices
293 */
294 public void copyTheirToTop(int [] rows) {
295 copyToTop(THEIR_ENTRIES, rows);
296 }
297
298 /**
299 * Copies the nodes given by indices in rows from the list of nodes in source to the
300 * list of merged nodes. Inserts the nodes at the end of the list of merged
301 * nodes.
302 *
303 * @param source the list of nodes to copy from
304 * @param rows the indices
305 */
306
307 public void copyToEnd(ListRole source, int [] rows) {
308 copy(source, rows, getMergedEntriesSize());
309 mergedEntriesSelectionModel.setSelectionInterval(getMergedEntriesSize()-rows.length, getMergedEntriesSize() -1);
310
311 }
312
313 /**
314 * Copies the nodes given by indices in rows from the list of my nodes to the
315 * list of merged nodes. Inserts the nodes at the end of the list of merged
316 * nodes.
317 *
318 * @param rows the indices
319 */
320 public void copyMyToEnd(int [] rows) {
321 copyToEnd(MY_ENTRIES, rows);
322 }
323
324 /**
325 * Copies the nodes given by indices in rows from the list of their nodes to the
326 * list of merged nodes. Inserts the nodes at the end of the list of merged
327 * nodes.
328 *
329 * @param rows the indices
330 */
331 public void copyTheirToEnd(int [] rows) {
332 copyToEnd(THEIR_ENTRIES, rows);
333 }
334
335 public void clearMerged() {
336 getMergedEntries().clear();
337 fireModelDataChanged();
338 }
339
340 protected final void initPopulate(OsmPrimitive my, OsmPrimitive their, Map<PrimitiveId, PrimitiveId> mergedMap) {
341 CheckParameterUtil.ensureParameterNotNull(my, "my");
342 CheckParameterUtil.ensureParameterNotNull(their, "their");
343 this.myDataset = my.getDataSet();
344 this.mergedMap = mergedMap;
345 getMergedEntries().clear();
346 getMyEntries().clear();
347 getTheirEntries().clear();
348 }
349
350 protected void alertCopyFailedForDeletedPrimitives(List<PrimitiveId> deletedIds) {
351 List<String> items = new ArrayList<>();
352 for (int i=0; i<Math.min(MAX_DELETED_PRIMITIVE_IN_DIALOG, deletedIds.size()); i++) {
353 items.add(deletedIds.get(i).toString());
354 }
355 if (deletedIds.size() > MAX_DELETED_PRIMITIVE_IN_DIALOG) {
356 items.add(tr("{0} more...", deletedIds.size() - MAX_DELETED_PRIMITIVE_IN_DIALOG));
357 }
358 StringBuilder sb = new StringBuilder();
359 sb.append("<html>");
360 sb.append(tr("The following objects could not be copied to the target object<br>because they are deleted in the target dataset:"));
361 sb.append(Utils.joinAsHtmlUnorderedList(items));
362 sb.append("</html>");
363 HelpAwareOptionPane.showOptionDialog(
364 Main.parent,
365 sb.toString(),
366 tr("Merging deleted objects failed"),
367 JOptionPane.WARNING_MESSAGE,
368 HelpUtil.ht("/Dialog/Conflict#MergingDeletedPrimitivesFailed")
369 );
370 }
371
372 private void copy(ListRole sourceRole, int[] rows, int position) {
373 if (position < 0 || position > getMergedEntriesSize())
374 throw new IllegalArgumentException("Position must be between 0 and "+getMergedEntriesSize()+" but is "+position);
375 List<T> newItems = new ArrayList<>(rows.length);
376 List<T> source = entries.get(sourceRole);
377 List<PrimitiveId> deletedIds = new ArrayList<>();
378 for (int row: rows) {
379 T entry = source.get(row);
380 OsmPrimitive primitive = getMyPrimitive(entry);
381 if (!primitive.isDeleted()) {
382 T clone = cloneEntryForMergedList(entry);
383 newItems.add(clone);
384 } else {
385 deletedIds.add(primitive.getPrimitiveId());
386 }
387 }
388 getMergedEntries().addAll(position, newItems);
389 fireModelDataChanged();
390 if (!deletedIds.isEmpty()) {
391 alertCopyFailedForDeletedPrimitives(deletedIds);
392 }
393 }
394
395 public void copyAll(ListRole source) {
396 getMergedEntries().clear();
397
398 int[] rows = new int[entries.get(source).size()];
399 for (int i=0; i<rows.length; i++) {
400 rows[i] = i;
401 }
402 copy(source, rows, 0);
403 }
404
405 /**
406 * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
407 * list of merged nodes. Inserts the nodes before row given by current.
408 *
409 * @param source the list of nodes to copy from
410 * @param rows the indices
411 * @param current the row index before which the nodes are inserted
412 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
413 */
414 protected void copyBeforeCurrent(ListRole source, int [] rows, int current) {
415 copy(source, rows, current);
416 mergedEntriesSelectionModel.setSelectionInterval(current, current + rows.length-1);
417 }
418
419 /**
420 * Copies the nodes given by indices in rows from the list of my nodes to the
421 * list of merged nodes. Inserts the nodes before row given by current.
422 *
423 * @param rows the indices
424 * @param current the row index before which the nodes are inserted
425 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
426 */
427 public void copyMyBeforeCurrent(int [] rows, int current) {
428 copyBeforeCurrent(MY_ENTRIES,rows,current);
429 }
430
431 /**
432 * Copies the nodes given by indices in rows from the list of their nodes to the
433 * list of merged nodes. Inserts the nodes before row given by current.
434 *
435 * @param rows the indices
436 * @param current the row index before which the nodes are inserted
437 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
438 */
439 public void copyTheirBeforeCurrent(int [] rows, int current) {
440 copyBeforeCurrent(THEIR_ENTRIES,rows,current);
441 }
442
443 /**
444 * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
445 * list of merged nodes. Inserts the nodes after the row given by current.
446 *
447 * @param source the list of nodes to copy from
448 * @param rows the indices
449 * @param current the row index after which the nodes are inserted
450 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
451 */
452 protected void copyAfterCurrent(ListRole source, int [] rows, int current) {
453 copy(source, rows, current + 1);
454 mergedEntriesSelectionModel.setSelectionInterval(current+1, current + rows.length-1);
455 notifyObservers();
456 }
457
458 /**
459 * Copies the nodes given by indices in rows from the list of my nodes to the
460 * list of merged nodes. Inserts the nodes after the row given by current.
461 *
462 * @param rows the indices
463 * @param current the row index after which the nodes are inserted
464 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
465 */
466 public void copyMyAfterCurrent(int [] rows, int current) {
467 copyAfterCurrent(MY_ENTRIES, rows, current);
468 }
469
470 /**
471 * Copies the nodes given by indices in rows from the list of my nodes to the
472 * list of merged nodes. Inserts the nodes after the row given by current.
473 *
474 * @param rows the indices
475 * @param current the row index after which the nodes are inserted
476 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
477 */
478 public void copyTheirAfterCurrent(int [] rows, int current) {
479 copyAfterCurrent(THEIR_ENTRIES, rows, current);
480 }
481
482 /**
483 * Moves the nodes given by indices in rows up by one position in the list
484 * of merged nodes.
485 *
486 * @param rows the indices
487 *
488 */
489 public void moveUpMerged(int [] rows) {
490 if (rows == null || rows.length == 0)
491 return;
492 if (rows[0] == 0)
493 // can't move up
494 return;
495 List<T> mergedEntries = getMergedEntries();
496 for (int row: rows) {
497 T n = mergedEntries.get(row);
498 mergedEntries.remove(row);
499 mergedEntries.add(row -1, n);
500 }
501 fireModelDataChanged();
502 notifyObservers();
503 mergedEntriesSelectionModel.clearSelection();
504 for (int row: rows) {
505 mergedEntriesSelectionModel.addSelectionInterval(row-1, row-1);
506 }
507 }
508
509 /**
510 * Moves the nodes given by indices in rows down by one position in the list
511 * of merged nodes.
512 *
513 * @param rows the indices
514 */
515 public void moveDownMerged(int [] rows) {
516 if (rows == null || rows.length == 0)
517 return;
518 List<T> mergedEntries = getMergedEntries();
519 if (rows[rows.length -1] == mergedEntries.size() -1)
520 // can't move down
521 return;
522 for (int i = rows.length-1; i>=0;i--) {
523 int row = rows[i];
524 T n = mergedEntries.get(row);
525 mergedEntries.remove(row);
526 mergedEntries.add(row +1, n);
527 }
528 fireModelDataChanged();
529 notifyObservers();
530 mergedEntriesSelectionModel.clearSelection();
531 for (int row: rows) {
532 mergedEntriesSelectionModel.addSelectionInterval(row+1, row+1);
533 }
534 }
535
536 /**
537 * Removes the nodes given by indices in rows from the list
538 * of merged nodes.
539 *
540 * @param rows the indices
541 */
542 public void removeMerged(int [] rows) {
543 if (rows == null || rows.length == 0)
544 return;
545
546 List<T> mergedEntries = getMergedEntries();
547
548 for (int i = rows.length-1; i>=0;i--) {
549 mergedEntries.remove(rows[i]);
550 }
551 fireModelDataChanged();
552 notifyObservers();
553 mergedEntriesSelectionModel.clearSelection();
554 }
555
556 /**
557 * Replies true if the list of my entries and the list of their
558 * entries are equal
559 *
560 * @return true, if the lists are equal; false otherwise
561 */
562 protected boolean myAndTheirEntriesEqual() {
563
564 if (getMyEntriesSize() != getTheirEntriesSize())
565 return false;
566 for (int i=0; i < getMyEntriesSize(); i++) {
567 if (! isEqualEntry(getMyEntries().get(i), getTheirEntries().get(i)))
568 return false;
569 }
570 return true;
571 }
572
573 /**
574 * This an adapter between a {@link JTable} and one of the three entry lists
575 * in the role {@link ListRole} managed by the {@link ListMergeModel}.
576 *
577 * From the point of view of the {@link JTable} it is a {@link TableModel}.
578 *
579 * @see ListMergeModel#getMyTableModel()
580 * @see ListMergeModel#getTheirTableModel()
581 * @see ListMergeModel#getMergedTableModel()
582 */
583 public class EntriesTableModel extends DefaultTableModel implements OsmPrimitivesTableModel {
584 private final ListRole role;
585
586 /**
587 *
588 * @param role the role
589 */
590 public EntriesTableModel(ListRole role) {
591 this.role = role;
592 }
593
594 @Override
595 public int getRowCount() {
596 int count = Math.max(getMyEntries().size(), getMergedEntries().size());
597 return Math.max(count, getTheirEntries().size());
598 }
599
600 @Override
601 public Object getValueAt(int row, int column) {
602 if (row < entries.get(role).size())
603 return entries.get(role).get(row);
604 return null;
605 }
606
607 @Override
608 public boolean isCellEditable(int row, int column) {
609 return false;
610 }
611
612 @Override
613 public void setValueAt(Object value, int row, int col) {
614 ListMergeModel.this.setValueAt(this, value,row,col);
615 }
616
617 public ListMergeModel<T> getListMergeModel() {
618 return ListMergeModel.this;
619 }
620
621 /**
622 * replies true if the {@link ListRole} of this {@link EntriesTableModel}
623 * participates in the current {@link ComparePairType}
624 *
625 * @return true, if the if the {@link ListRole} of this {@link EntriesTableModel}
626 * participates in the current {@link ComparePairType}
627 *
628 * @see ListMergeModel.ComparePairListModel#getSelectedComparePair()
629 */
630 public boolean isParticipatingInCurrentComparePair() {
631 return getComparePairListModel()
632 .getSelectedComparePair()
633 .isParticipatingIn(role);
634 }
635
636 /**
637 * replies true if the entry at <code>row</code> is equal to the entry at the
638 * same position in the opposite list of the current {@link ComparePairType}.
639 *
640 * @param row the row number
641 * @return true if the entry at <code>row</code> is equal to the entry at the
642 * same position in the opposite list of the current {@link ComparePairType}
643 * @throws IllegalStateException if this model is not participating in the
644 * current {@link ComparePairType}
645 * @see ComparePairType#getOppositeRole(ListRole)
646 * @see #getRole()
647 * @see #getOppositeEntries()
648 */
649 public boolean isSamePositionInOppositeList(int row) {
650 if (!isParticipatingInCurrentComparePair())
651 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString()));
652 if (row >= getEntries().size()) return false;
653 if (row >= getOppositeEntries().size()) return false;
654
655 T e1 = getEntries().get(row);
656 T e2 = getOppositeEntries().get(row);
657 return isEqualEntry(e1, e2);
658 }
659
660 /**
661 * replies true if the entry at the current position is present in the opposite list
662 * of the current {@link ComparePairType}.
663 *
664 * @param row the current row
665 * @return true if the entry at the current position is present in the opposite list
666 * of the current {@link ComparePairType}.
667 * @throws IllegalStateException if this model is not participating in the
668 * current {@link ComparePairType}
669 * @see ComparePairType#getOppositeRole(ListRole)
670 * @see #getRole()
671 * @see #getOppositeEntries()
672 */
673 public boolean isIncludedInOppositeList(int row) {
674 if (!isParticipatingInCurrentComparePair())
675 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString()));
676
677 if (row >= getEntries().size()) return false;
678 T e1 = getEntries().get(row);
679 for (T e2: getOppositeEntries()) {
680 if (isEqualEntry(e1, e2)) return true;
681 }
682 return false;
683 }
684
685 protected List<T> getEntries() {
686 return entries.get(role);
687 }
688
689 /**
690 * replies the opposite list of entries with respect to the current {@link ComparePairType}
691 *
692 * @return the opposite list of entries
693 */
694 protected List<T> getOppositeEntries() {
695 ListRole opposite = getComparePairListModel().getSelectedComparePair().getOppositeRole(role);
696 return entries.get(opposite);
697 }
698
699 public ListRole getRole() {
700 return role;
701 }
702
703 @Override
704 public OsmPrimitive getReferredPrimitive(int idx) {
705 Object value = getValueAt(idx, 1);
706 if (value instanceof OsmPrimitive) {
707 return (OsmPrimitive) value;
708 } else if (value instanceof RelationMember) {
709 return ((RelationMember)value).getMember();
710 } else {
711 Main.error("Unknown object type: "+value);
712 return null;
713 }
714 }
715 }
716
717 /**
718 * This is the selection model to be used in a {@link JTable} which displays
719 * an entry list managed by {@link ListMergeModel}.
720 *
721 * The model ensures that only rows displaying an entry in the entry list
722 * can be selected. "Empty" rows can't be selected.
723 *
724 * @see ListMergeModel#getMySelectionModel()
725 * @see ListMergeModel#getMergedSelectionModel()
726 * @see ListMergeModel#getTheirSelectionModel()
727 *
728 */
729 protected class EntriesSelectionModel extends DefaultListSelectionModel {
730 private final transient List<T> entries;
731
732 public EntriesSelectionModel(List<T> nodes) {
733 this.entries = nodes;
734 }
735
736 @Override
737 public void addSelectionInterval(int index0, int index1) {
738 if (entries.isEmpty()) return;
739 if (index0 > entries.size() - 1) return;
740 index0 = Math.min(entries.size()-1, index0);
741 index1 = Math.min(entries.size()-1, index1);
742 super.addSelectionInterval(index0, index1);
743 }
744
745 @Override
746 public void insertIndexInterval(int index, int length, boolean before) {
747 if (entries.isEmpty()) return;
748 if (before) {
749 int newindex = Math.min(entries.size()-1, index);
750 if (newindex < index - length) return;
751 length = length - (index - newindex);
752 super.insertIndexInterval(newindex, length, before);
753 } else {
754 if (index > entries.size() -1) return;
755 length = Math.min(entries.size()-1 - index, length);
756 super.insertIndexInterval(index, length, before);
757 }
758 }
759
760 @Override
761 public void moveLeadSelectionIndex(int leadIndex) {
762 if (entries.isEmpty()) return;
763 leadIndex = Math.max(0, leadIndex);
764 leadIndex = Math.min(entries.size() - 1, leadIndex);
765 super.moveLeadSelectionIndex(leadIndex);
766 }
767
768 @Override
769 public void removeIndexInterval(int index0, int index1) {
770 if (entries.isEmpty()) return;
771 index0 = Math.max(0, index0);
772 index0 = Math.min(entries.size() - 1, index0);
773
774 index1 = Math.max(0, index1);
775 index1 = Math.min(entries.size() - 1, index1);
776 super.removeIndexInterval(index0, index1);
777 }
778
779 @Override
780 public void removeSelectionInterval(int index0, int index1) {
781 if (entries.isEmpty()) return;
782 index0 = Math.max(0, index0);
783 index0 = Math.min(entries.size() - 1, index0);
784
785 index1 = Math.max(0, index1);
786 index1 = Math.min(entries.size() - 1, index1);
787 super.removeSelectionInterval(index0, index1);
788 }
789
790 @Override
791 public void setAnchorSelectionIndex(int anchorIndex) {
792 if (entries.isEmpty()) return;
793 anchorIndex = Math.min(entries.size() - 1, anchorIndex);
794 super.setAnchorSelectionIndex(anchorIndex);
795 }
796
797 @Override
798 public void setLeadSelectionIndex(int leadIndex) {
799 if (entries.isEmpty()) return;
800 leadIndex = Math.min(entries.size() - 1, leadIndex);
801 super.setLeadSelectionIndex(leadIndex);
802 }
803
804 @Override
805 public void setSelectionInterval(int index0, int index1) {
806 if (entries.isEmpty()) return;
807 index0 = Math.max(0, index0);
808 index0 = Math.min(entries.size() - 1, index0);
809
810 index1 = Math.max(0, index1);
811 index1 = Math.min(entries.size() - 1, index1);
812
813 super.setSelectionInterval(index0, index1);
814 }
815 }
816
817 public ComparePairListModel getComparePairListModel() {
818 return this.comparePairListModel;
819 }
820
821 public class ComparePairListModel extends AbstractListModel<ComparePairType> implements ComboBoxModel<ComparePairType> {
822
823 private int selectedIdx;
824 private final transient List<ComparePairType> compareModes;
825
826 /**
827 * Constructs a new {@code ComparePairListModel}.
828 */
829 public ComparePairListModel() {
830 this.compareModes = new ArrayList<>();
831 compareModes.add(MY_WITH_THEIR);
832 compareModes.add(MY_WITH_MERGED);
833 compareModes.add(THEIR_WITH_MERGED);
834 selectedIdx = 0;
835 }
836
837 @Override
838 public ComparePairType getElementAt(int index) {
839 if (index < compareModes.size())
840 return compareModes.get(index);
841 throw new IllegalArgumentException(tr("Unexpected value of parameter ''index''. Got {0}.", index));
842 }
843
844 @Override
845 public int getSize() {
846 return compareModes.size();
847 }
848
849 @Override
850 public Object getSelectedItem() {
851 return compareModes.get(selectedIdx);
852 }
853
854 @Override
855 public void setSelectedItem(Object anItem) {
856 int i = compareModes.indexOf(anItem);
857 if (i < 0)
858 throw new IllegalStateException(tr("Item {0} not found in list.", anItem));
859 selectedIdx = i;
860 fireModelDataChanged();
861 }
862
863 public ComparePairType getSelectedComparePair() {
864 return compareModes.get(selectedIdx);
865 }
866 }
867}
Note: See TracBrowser for help on using the repository browser.