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