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

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

checkstyle

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