001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.conflict.pair; 003 004import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_MERGED; 005import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_THEIR; 006import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.THEIR_WITH_MERGED; 007import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MERGED_ENTRIES; 008import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MY_ENTRIES; 009import static org.openstreetmap.josm.gui.conflict.pair.ListRole.THEIR_ENTRIES; 010import static org.openstreetmap.josm.tools.I18n.tr; 011 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014import java.util.ArrayList; 015import java.util.HashMap; 016import java.util.List; 017import java.util.Map; 018import java.util.Observable; 019 020import javax.swing.AbstractListModel; 021import javax.swing.ComboBoxModel; 022import javax.swing.DefaultListSelectionModel; 023import javax.swing.JOptionPane; 024import javax.swing.JTable; 025import javax.swing.ListSelectionModel; 026import javax.swing.table.DefaultTableModel; 027import javax.swing.table.TableModel; 028 029import org.openstreetmap.josm.Main; 030import org.openstreetmap.josm.data.osm.DataSet; 031import org.openstreetmap.josm.data.osm.OsmPrimitive; 032import org.openstreetmap.josm.data.osm.PrimitiveId; 033import org.openstreetmap.josm.data.osm.RelationMember; 034import org.openstreetmap.josm.gui.HelpAwareOptionPane; 035import org.openstreetmap.josm.gui.help.HelpUtil; 036import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel; 037import org.openstreetmap.josm.tools.CheckParameterUtil; 038import org.openstreetmap.josm.tools.Utils; 039 040/** 041 * ListMergeModel is a model for interactively comparing and merging two list of entries 042 * of type T. It maintains three lists of entries of type T: 043 * <ol> 044 * <li>the list of <em>my</em> entries</li> 045 * <li>the list of <em>their</em> entries</li> 046 * <li>the list of <em>merged</em> entries</li> 047 * </ol> 048 * 049 * A ListMergeModel is a factory for three {@link TableModel}s and three {@link ListSelectionModel}s: 050 * <ol> 051 * <li>the table model and the list selection for for a {@link JTable} which shows my entries. 052 * See {@link #getMyTableModel()} and {@link ListMergeModel#getMySelectionModel()}</li> 053 * <li>dito for their entries and merged entries</li> 054 * </ol> 055 * 056 * A ListMergeModel can be ''frozen''. If it's frozen, it doesn't accept additional merge 057 * decisions. {@link PropertyChangeListener}s can register for property value changes of 058 * {@link #FROZEN_PROP}. 059 * 060 * ListMergeModel is an abstract class. Three methods have to be implemented by subclasses: 061 * <ul> 062 * <li>{@link ListMergeModel#cloneEntryForMergedList} - clones an entry of type T</li> 063 * <li>{@link ListMergeModel#isEqualEntry} - checks whether two entries are equals </li> 064 * <li>{@link ListMergeModel#setValueAt(DefaultTableModel, Object, int, int)} - handles values edited in 065 * a JTable, dispatched from {@link TableModel#setValueAt(Object, int, int)} </li> 066 * </ul> 067 * A ListMergeModel is used in combination with a {@link ListMerger}. 068 * 069 * @param <T> the type of the list entries 070 * @see ListMerger 071 */ 072public abstract class ListMergeModel<T extends PrimitiveId> extends Observable { 073 public static final String FROZEN_PROP = ListMergeModel.class.getName() + ".frozen"; 074 075 private static final int MAX_DELETED_PRIMITIVE_IN_DIALOG = 5; 076 077 protected Map<ListRole, ArrayList<T>> entries; 078 079 protected EntriesTableModel myEntriesTableModel; 080 protected EntriesTableModel theirEntriesTableModel; 081 protected EntriesTableModel mergedEntriesTableModel; 082 083 protected EntriesSelectionModel myEntriesSelectionModel; 084 protected EntriesSelectionModel theirEntriesSelectionModel; 085 protected EntriesSelectionModel mergedEntriesSelectionModel; 086 087 private final List<PropertyChangeListener> listeners; 088 private boolean isFrozen = false; 089 private final ComparePairListModel comparePairListModel; 090 091 private DataSet myDataset; 092 private Map<PrimitiveId, PrimitiveId> mergedMap; 093 094 /** 095 * Creates a clone of an entry of type T suitable to be included in the 096 * list of merged entries 097 * 098 * @param entry the entry 099 * @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(); 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 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 413 * 414 */ 415 protected void copyBeforeCurrent(ListRole source, int [] rows, int current) { 416 copy(source, rows, current); 417 mergedEntriesSelectionModel.setSelectionInterval(current, current + rows.length-1); 418 } 419 420 /** 421 * Copies the nodes given by indices in rows from the list of my nodes to the 422 * list of merged nodes. Inserts the nodes before row given by current. 423 * 424 * @param rows the indices 425 * @param current the row index before which the nodes are inserted 426 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 427 * 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 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 440 * 441 */ 442 public void copyTheirBeforeCurrent(int [] rows, int current) { 443 copyBeforeCurrent(THEIR_ENTRIES,rows,current); 444 } 445 446 /** 447 * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the 448 * list of merged nodes. Inserts the nodes after the row given by current. 449 * 450 * @param source the list of nodes to copy from 451 * @param rows the indices 452 * @param current the row index after which the nodes are inserted 453 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 454 * 455 */ 456 protected void copyAfterCurrent(ListRole source, int [] rows, int current) { 457 copy(source, rows, current + 1); 458 mergedEntriesSelectionModel.setSelectionInterval(current+1, current + rows.length-1); 459 notifyObservers(); 460 } 461 462 /** 463 * Copies the nodes given by indices in rows from the list of my nodes to the 464 * list of merged nodes. Inserts the nodes after the row given by current. 465 * 466 * @param rows the indices 467 * @param current the row index after which the nodes are inserted 468 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 469 * 470 */ 471 public void copyMyAfterCurrent(int [] rows, int current) { 472 copyAfterCurrent(MY_ENTRIES, rows, current); 473 } 474 475 /** 476 * Copies the nodes given by indices in rows from the list of my nodes to the 477 * list of merged nodes. Inserts the nodes after the row given by current. 478 * 479 * @param rows the indices 480 * @param current the row index after which the nodes are inserted 481 * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 482 * 483 */ 484 public void copyTheirAfterCurrent(int [] rows, int current) { 485 copyAfterCurrent(THEIR_ENTRIES, rows, current); 486 } 487 488 /** 489 * Moves the nodes given by indices in rows up by one position in the list 490 * of merged nodes. 491 * 492 * @param rows the indices 493 * 494 */ 495 public void moveUpMerged(int [] rows) { 496 if (rows == null || rows.length == 0) 497 return; 498 if (rows[0] == 0) 499 // can't move up 500 return; 501 List<T> mergedEntries = getMergedEntries(); 502 for (int row: rows) { 503 T n = mergedEntries.get(row); 504 mergedEntries.remove(row); 505 mergedEntries.add(row -1, n); 506 } 507 fireModelDataChanged(); 508 notifyObservers(); 509 mergedEntriesSelectionModel.clearSelection(); 510 for (int row: rows) { 511 mergedEntriesSelectionModel.addSelectionInterval(row-1, row-1); 512 } 513 } 514 515 /** 516 * Moves the nodes given by indices in rows down by one position in the list 517 * of merged nodes. 518 * 519 * @param rows the indices 520 */ 521 public void moveDownMerged(int [] rows) { 522 if (rows == null || rows.length == 0) 523 return; 524 List<T> mergedEntries = getMergedEntries(); 525 if (rows[rows.length -1] == mergedEntries.size() -1) 526 // can't move down 527 return; 528 for (int i = rows.length-1; i>=0;i--) { 529 int row = rows[i]; 530 T n = mergedEntries.get(row); 531 mergedEntries.remove(row); 532 mergedEntries.add(row +1, n); 533 } 534 fireModelDataChanged(); 535 notifyObservers(); 536 mergedEntriesSelectionModel.clearSelection(); 537 for (int row: rows) { 538 mergedEntriesSelectionModel.addSelectionInterval(row+1, row+1); 539 } 540 } 541 542 /** 543 * Removes the nodes given by indices in rows from the list 544 * of merged nodes. 545 * 546 * @param rows the indices 547 */ 548 public void removeMerged(int [] rows) { 549 if (rows == null || rows.length == 0) 550 return; 551 552 List<T> mergedEntries = getMergedEntries(); 553 554 for (int i = rows.length-1; i>=0;i--) { 555 mergedEntries.remove(rows[i]); 556 } 557 fireModelDataChanged(); 558 notifyObservers(); 559 mergedEntriesSelectionModel.clearSelection(); 560 } 561 562 /** 563 * Replies true if the list of my entries and the list of their 564 * entries are equal 565 * 566 * @return true, if the lists are equal; false otherwise 567 */ 568 protected boolean myAndTheirEntriesEqual() { 569 570 if (getMyEntriesSize() != getTheirEntriesSize()) 571 return false; 572 for (int i=0; i < getMyEntriesSize(); i++) { 573 if (! isEqualEntry(getMyEntries().get(i), getTheirEntries().get(i))) 574 return false; 575 } 576 return true; 577 } 578 579 /** 580 * This an adapter between a {@link JTable} and one of the three entry lists 581 * in the role {@link ListRole} managed by the {@link ListMergeModel}. 582 * 583 * From the point of view of the {@link JTable} it is a {@link TableModel}. 584 * 585 * @see ListMergeModel#getMyTableModel() 586 * @see ListMergeModel#getTheirTableModel() 587 * @see ListMergeModel#getMergedTableModel() 588 */ 589 public class EntriesTableModel extends DefaultTableModel implements OsmPrimitivesTableModel { 590 private final ListRole role; 591 592 /** 593 * 594 * @param role the role 595 */ 596 public EntriesTableModel(ListRole role) { 597 this.role = role; 598 } 599 600 @Override 601 public int getRowCount() { 602 int count = Math.max(getMyEntries().size(), getMergedEntries().size()); 603 count = Math.max(count, getTheirEntries().size()); 604 return count; 605 } 606 607 @Override 608 public Object getValueAt(int row, int column) { 609 if (row < entries.get(role).size()) 610 return entries.get(role).get(row); 611 return null; 612 } 613 614 @Override 615 public boolean isCellEditable(int row, int column) { 616 return false; 617 } 618 619 @Override 620 public void setValueAt(Object value, int row, int col) { 621 ListMergeModel.this.setValueAt(this, value,row,col); 622 } 623 624 public ListMergeModel<T> getListMergeModel() { 625 return ListMergeModel.this; 626 } 627 628 /** 629 * replies true if the {@link ListRole} of this {@link EntriesTableModel} 630 * participates in the current {@link ComparePairType} 631 * 632 * @return true, if the if the {@link ListRole} of this {@link EntriesTableModel} 633 * participates in the current {@link ComparePairType} 634 * 635 * @see ListMergeModel.ComparePairListModel#getSelectedComparePair() 636 */ 637 public boolean isParticipatingInCurrentComparePair() { 638 return getComparePairListModel() 639 .getSelectedComparePair() 640 .isParticipatingIn(role); 641 } 642 643 /** 644 * replies true if the entry at <code>row</code> is equal to the entry at the 645 * same position in the opposite list of the current {@link ComparePairType}. 646 * 647 * @param row the row number 648 * @return true if the entry at <code>row</code> is equal to the entry at the 649 * same position in the opposite list of the current {@link ComparePairType} 650 * @exception IllegalStateException thrown, if this model is not participating in the 651 * current {@link ComparePairType} 652 * @see ComparePairType#getOppositeRole(ListRole) 653 * @see #getRole() 654 * @see #getOppositeEntries() 655 */ 656 public boolean isSamePositionInOppositeList(int row) { 657 if (!isParticipatingInCurrentComparePair()) 658 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString())); 659 if (row >= getEntries().size()) return false; 660 if (row >= getOppositeEntries().size()) return false; 661 662 T e1 = getEntries().get(row); 663 T e2 = getOppositeEntries().get(row); 664 return isEqualEntry(e1, e2); 665 } 666 667 /** 668 * replies true if the entry at the current position is present in the opposite list 669 * of the current {@link ComparePairType}. 670 * 671 * @param row the current row 672 * @return true if the entry at the current position is present in the opposite list 673 * of the current {@link ComparePairType}. 674 * @exception IllegalStateException thrown, if this model is not participating in the 675 * current {@link ComparePairType} 676 * @see ComparePairType#getOppositeRole(ListRole) 677 * @see #getRole() 678 * @see #getOppositeEntries() 679 */ 680 public boolean isIncludedInOppositeList(int row) { 681 if (!isParticipatingInCurrentComparePair()) 682 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString())); 683 684 if (row >= getEntries().size()) return false; 685 T e1 = getEntries().get(row); 686 for (T e2: getOppositeEntries()) { 687 if (isEqualEntry(e1, e2)) return true; 688 } 689 return false; 690 } 691 692 protected List<T> getEntries() { 693 return entries.get(role); 694 } 695 696 /** 697 * replies the opposite list of entries with respect to the current {@link ComparePairType} 698 * 699 * @return the opposite list of entries 700 */ 701 protected List<T> getOppositeEntries() { 702 ListRole opposite = getComparePairListModel().getSelectedComparePair().getOppositeRole(role); 703 return entries.get(opposite); 704 } 705 706 public ListRole getRole() { 707 return role; 708 } 709 710 @Override 711 public OsmPrimitive getReferredPrimitive(int idx) { 712 Object value = getValueAt(idx, 1); 713 if (value instanceof OsmPrimitive) { 714 return (OsmPrimitive) value; 715 } else if (value instanceof RelationMember) { 716 return ((RelationMember)value).getMember(); 717 } else { 718 Main.error("Unknown object type: "+value); 719 return null; 720 } 721 } 722 } 723 724 /** 725 * This is the selection model to be used in a {@link JTable} which displays 726 * an entry list managed by {@link ListMergeModel}. 727 * 728 * The model ensures that only rows displaying an entry in the entry list 729 * can be selected. "Empty" rows can't be selected. 730 * 731 * @see ListMergeModel#getMySelectionModel() 732 * @see ListMergeModel#getMergedSelectionModel() 733 * @see ListMergeModel#getTheirSelectionModel() 734 * 735 */ 736 protected class EntriesSelectionModel extends DefaultListSelectionModel { 737 private final List<T> entries; 738 739 public EntriesSelectionModel(ArrayList<T> nodes) { 740 this.entries = nodes; 741 } 742 743 @Override 744 public void addSelectionInterval(int index0, int index1) { 745 if (entries.isEmpty()) return; 746 if (index0 > entries.size() - 1) return; 747 index0 = Math.min(entries.size()-1, index0); 748 index1 = Math.min(entries.size()-1, index1); 749 super.addSelectionInterval(index0, index1); 750 } 751 752 @Override 753 public void insertIndexInterval(int index, int length, boolean before) { 754 if (entries.isEmpty()) return; 755 if (before) { 756 int newindex = Math.min(entries.size()-1, index); 757 if (newindex < index - length) return; 758 length = length - (index - newindex); 759 super.insertIndexInterval(newindex, length, before); 760 } else { 761 if (index > entries.size() -1) return; 762 length = Math.min(entries.size()-1 - index, length); 763 super.insertIndexInterval(index, length, before); 764 } 765 } 766 767 @Override 768 public void moveLeadSelectionIndex(int leadIndex) { 769 if (entries.isEmpty()) return; 770 leadIndex = Math.max(0, leadIndex); 771 leadIndex = Math.min(entries.size() - 1, leadIndex); 772 super.moveLeadSelectionIndex(leadIndex); 773 } 774 775 @Override 776 public void removeIndexInterval(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.removeIndexInterval(index0, index1); 784 } 785 786 @Override 787 public void removeSelectionInterval(int index0, int index1) { 788 if (entries.isEmpty()) return; 789 index0 = Math.max(0, index0); 790 index0 = Math.min(entries.size() - 1, index0); 791 792 index1 = Math.max(0, index1); 793 index1 = Math.min(entries.size() - 1, index1); 794 super.removeSelectionInterval(index0, index1); 795 } 796 797 @Override 798 public void setAnchorSelectionIndex(int anchorIndex) { 799 if (entries.isEmpty()) return; 800 anchorIndex = Math.min(entries.size() - 1, anchorIndex); 801 super.setAnchorSelectionIndex(anchorIndex); 802 } 803 804 @Override 805 public void setLeadSelectionIndex(int leadIndex) { 806 if (entries.isEmpty()) return; 807 leadIndex = Math.min(entries.size() - 1, leadIndex); 808 super.setLeadSelectionIndex(leadIndex); 809 } 810 811 @Override 812 public void setSelectionInterval(int index0, int index1) { 813 if (entries.isEmpty()) return; 814 index0 = Math.max(0, index0); 815 index0 = Math.min(entries.size() - 1, index0); 816 817 index1 = Math.max(0, index1); 818 index1 = Math.min(entries.size() - 1, index1); 819 820 super.setSelectionInterval(index0, index1); 821 } 822 } 823 824 public ComparePairListModel getComparePairListModel() { 825 return this.comparePairListModel; 826 } 827 828 public class ComparePairListModel extends AbstractListModel<ComparePairType> implements ComboBoxModel<ComparePairType> { 829 830 private int selectedIdx; 831 private final List<ComparePairType> compareModes; 832 833 /** 834 * Constructs a new {@code ComparePairListModel}. 835 */ 836 public ComparePairListModel() { 837 this.compareModes = new ArrayList<>(); 838 compareModes.add(MY_WITH_THEIR); 839 compareModes.add(MY_WITH_MERGED); 840 compareModes.add(THEIR_WITH_MERGED); 841 selectedIdx = 0; 842 } 843 844 @Override 845 public ComparePairType getElementAt(int index) { 846 if (index < compareModes.size()) 847 return compareModes.get(index); 848 throw new IllegalArgumentException(tr("Unexpected value of parameter ''index''. Got {0}.", index)); 849 } 850 851 @Override 852 public int getSize() { 853 return compareModes.size(); 854 } 855 856 @Override 857 public Object getSelectedItem() { 858 return compareModes.get(selectedIdx); 859 } 860 861 @Override 862 public void setSelectedItem(Object anItem) { 863 int i = compareModes.indexOf(anItem); 864 if (i < 0) 865 throw new IllegalStateException(tr("Item {0} not found in list.", anItem)); 866 selectedIdx = i; 867 fireModelDataChanged(); 868 } 869 870 public ComparePairType getSelectedComparePair() { 871 return compareModes.get(selectedIdx); 872 } 873 } 874}