001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.conflict.pair; 003 004 import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_MERGED; 005 import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_THEIR; 006 import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.THEIR_WITH_MERGED; 007 import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MERGED_ENTRIES; 008 import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MY_ENTRIES; 009 import static org.openstreetmap.josm.gui.conflict.pair.ListRole.THEIR_ENTRIES; 010 import static org.openstreetmap.josm.tools.I18n.tr; 011 012 import java.beans.PropertyChangeEvent; 013 import java.beans.PropertyChangeListener; 014 import java.util.ArrayList; 015 import java.util.HashMap; 016 import java.util.List; 017 import java.util.Map; 018 import java.util.Observable; 019 020 import javax.swing.AbstractListModel; 021 import javax.swing.ComboBoxModel; 022 import javax.swing.DefaultListSelectionModel; 023 import javax.swing.JOptionPane; 024 import javax.swing.JTable; 025 import javax.swing.ListSelectionModel; 026 import javax.swing.table.DefaultTableModel; 027 import javax.swing.table.TableModel; 028 029 import org.openstreetmap.josm.Main; 030 import org.openstreetmap.josm.data.osm.DataSet; 031 import org.openstreetmap.josm.data.osm.OsmPrimitive; 032 import org.openstreetmap.josm.data.osm.PrimitiveId; 033 import org.openstreetmap.josm.data.osm.RelationMember; 034 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 035 import org.openstreetmap.josm.gui.help.HelpUtil; 036 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel; 037 import 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 #PROP_FROZEN}. 058 * 059 * ListMergeModel is an abstract class. Three methods have to be implemented by subclasses: 060 * <ul> 061 * <li>{@link ListMergeModel#cloneEntryForMergedList(Object)} - clones an entry of type T</li> 062 * <li>{@link ListMergeModel#isEqualEntry(Object, Object)} - 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 */ 071 public 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 HashMap<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 * @param <T> 589 * @see ListMergeModel#getMyTableModel() 590 * @see ListMergeModel#getTheirTableModel() 591 * @see ListMergeModel#getMergedTableModel() 592 */ 593 public class EntriesTableModel extends DefaultTableModel implements OsmPrimitivesTableModel { 594 private final ListRole role; 595 596 /** 597 * 598 * @param role the role 599 */ 600 public EntriesTableModel(ListRole role) { 601 this.role = role; 602 } 603 604 @Override 605 public int getRowCount() { 606 int count = Math.max(getMyEntries().size(), getMergedEntries().size()); 607 count = Math.max(count, getTheirEntries().size()); 608 return count; 609 } 610 611 @Override 612 public Object getValueAt(int row, int column) { 613 if (row < entries.get(role).size()) 614 return entries.get(role).get(row); 615 return null; 616 } 617 618 @Override 619 public boolean isCellEditable(int row, int column) { 620 return false; 621 } 622 623 @Override 624 public void setValueAt(Object value, int row, int col) { 625 ListMergeModel.this.setValueAt(this, value,row,col); 626 } 627 628 public ListMergeModel<T> getListMergeModel() { 629 return ListMergeModel.this; 630 } 631 632 /** 633 * replies true if the {@link ListRole} of this {@link EntriesTableModel} 634 * participates in the current {@link ComparePairType} 635 * 636 * @return true, if the if the {@link ListRole} of this {@link EntriesTableModel} 637 * participates in the current {@link ComparePairType} 638 * 639 * @see ComparePairListModel#getSelectedComparePair() 640 */ 641 public boolean isParticipatingInCurrentComparePair() { 642 return getComparePairListModel() 643 .getSelectedComparePair() 644 .isParticipatingIn(role); 645 } 646 647 /** 648 * replies 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 * 651 * @param row the row number 652 * @return true if the entry at <code>row</code> is equal to the entry at the 653 * same position in the opposite list of the current {@link ComparePairType} 654 * @exception IllegalStateException thrown, if this model is not participating in the 655 * current {@link ComparePairType} 656 * @see ComparePairType#getOppositeRole(ListRole) 657 * @see #getRole() 658 * @see #getOppositeEntries() 659 */ 660 public boolean isSamePositionInOppositeList(int row) { 661 if (!isParticipatingInCurrentComparePair()) 662 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString())); 663 if (row >= getEntries().size()) return false; 664 if (row >= getOppositeEntries().size()) return false; 665 666 T e1 = getEntries().get(row); 667 T e2 = getOppositeEntries().get(row); 668 return isEqualEntry(e1, e2); 669 } 670 671 /** 672 * replies true if the entry at the current position is present in the opposite list 673 * of the current {@link ComparePairType}. 674 * 675 * @param row the current row 676 * @return true if the entry at the current position is present in the opposite list 677 * of the current {@link ComparePairType}. 678 * @exception IllegalStateException thrown, if this model is not participating in the 679 * current {@link ComparePairType} 680 * @see ComparePairType#getOppositeRole(ListRole) 681 * @see #getRole() 682 * @see #getOppositeEntries() 683 */ 684 public boolean isIncludedInOppositeList(int row) { 685 if (!isParticipatingInCurrentComparePair()) 686 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString())); 687 688 if (row >= getEntries().size()) return false; 689 T e1 = getEntries().get(row); 690 for (T e2: getOppositeEntries()) { 691 if (isEqualEntry(e1, e2)) return true; 692 } 693 return false; 694 } 695 696 protected ArrayList<T> getEntries() { 697 return entries.get(role); 698 } 699 700 /** 701 * replies the opposite list of entries with respect to the current {@link ComparePairType} 702 * 703 * @return the opposite list of entries 704 */ 705 protected ArrayList<T> getOppositeEntries() { 706 ListRole opposite = getComparePairListModel().getSelectedComparePair().getOppositeRole(role); 707 return entries.get(opposite); 708 } 709 710 public ListRole getRole() { 711 return role; 712 } 713 714 @Override 715 public OsmPrimitive getReferredPrimitive(int idx) { 716 Object value = getValueAt(idx, 1); 717 if (value instanceof OsmPrimitive) { 718 return (OsmPrimitive) value; 719 } else if (value instanceof RelationMember) { 720 return ((RelationMember)value).getMember(); 721 } else { 722 System.err.println("Unknown object type: "+value); 723 return null; 724 } 725 } 726 } 727 728 /** 729 * This is the selection model to be used in a {@link JTable} which displays 730 * an entry list managed by {@link ListMergeModel}. 731 * 732 * The model ensures that only rows displaying an entry in the entry list 733 * can be selected. "Empty" rows can't be selected. 734 * 735 * @see ListMergeModel#getMySelectionModel() 736 * @see ListMergeModel#getMergedSelectionModel() 737 * @see ListMergeModel#getTheirSelectionModel() 738 * 739 */ 740 protected class EntriesSelectionModel extends DefaultListSelectionModel { 741 private final ArrayList<T> entries; 742 743 public EntriesSelectionModel(ArrayList<T> nodes) { 744 this.entries = nodes; 745 } 746 747 @Override 748 public void addSelectionInterval(int index0, int index1) { 749 if (entries.isEmpty()) return; 750 if (index0 > entries.size() - 1) return; 751 index0 = Math.min(entries.size()-1, index0); 752 index1 = Math.min(entries.size()-1, index1); 753 super.addSelectionInterval(index0, index1); 754 } 755 756 @Override 757 public void insertIndexInterval(int index, int length, boolean before) { 758 if (entries.isEmpty()) return; 759 if (before) { 760 int newindex = Math.min(entries.size()-1, index); 761 if (newindex < index - length) return; 762 length = length - (index - newindex); 763 super.insertIndexInterval(newindex, length, before); 764 } else { 765 if (index > entries.size() -1) return; 766 length = Math.min(entries.size()-1 - index, length); 767 super.insertIndexInterval(index, length, before); 768 } 769 } 770 771 @Override 772 public void moveLeadSelectionIndex(int leadIndex) { 773 if (entries.isEmpty()) return; 774 leadIndex = Math.max(0, leadIndex); 775 leadIndex = Math.min(entries.size() - 1, leadIndex); 776 super.moveLeadSelectionIndex(leadIndex); 777 } 778 779 @Override 780 public void removeIndexInterval(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.removeIndexInterval(index0, index1); 788 } 789 790 @Override 791 public void removeSelectionInterval(int index0, int index1) { 792 if (entries.isEmpty()) return; 793 index0 = Math.max(0, index0); 794 index0 = Math.min(entries.size() - 1, index0); 795 796 index1 = Math.max(0, index1); 797 index1 = Math.min(entries.size() - 1, index1); 798 super.removeSelectionInterval(index0, index1); 799 } 800 801 @Override 802 public void setAnchorSelectionIndex(int anchorIndex) { 803 if (entries.isEmpty()) return; 804 anchorIndex = Math.min(entries.size() - 1, anchorIndex); 805 super.setAnchorSelectionIndex(anchorIndex); 806 } 807 808 @Override 809 public void setLeadSelectionIndex(int leadIndex) { 810 if (entries.isEmpty()) return; 811 leadIndex = Math.min(entries.size() - 1, leadIndex); 812 super.setLeadSelectionIndex(leadIndex); 813 } 814 815 @Override 816 public void setSelectionInterval(int index0, int index1) { 817 if (entries.isEmpty()) return; 818 index0 = Math.max(0, index0); 819 index0 = Math.min(entries.size() - 1, index0); 820 821 index1 = Math.max(0, index1); 822 index1 = Math.min(entries.size() - 1, index1); 823 824 super.setSelectionInterval(index0, index1); 825 } 826 } 827 828 public ComparePairListModel getComparePairListModel() { 829 return this.comparePairListModel; 830 } 831 832 public class ComparePairListModel extends AbstractListModel implements ComboBoxModel { 833 834 private int selectedIdx; 835 private final ArrayList<ComparePairType> compareModes; 836 837 public ComparePairListModel() { 838 this.compareModes = new ArrayList<ComparePairType>(); 839 compareModes.add(MY_WITH_THEIR); 840 compareModes.add(MY_WITH_MERGED); 841 compareModes.add(THEIR_WITH_MERGED); 842 selectedIdx = 0; 843 } 844 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 public int getSize() { 852 return compareModes.size(); 853 } 854 855 public Object getSelectedItem() { 856 return compareModes.get(selectedIdx); 857 } 858 859 public void setSelectedItem(Object anItem) { 860 int i = compareModes.indexOf(anItem); 861 if (i < 0) 862 throw new IllegalStateException(tr("Item {0} not found in list.", anItem)); 863 selectedIdx = i; 864 fireModelDataChanged(); 865 } 866 867 public ComparePairType getSelectedComparePair() { 868 return compareModes.get(selectedIdx); 869 } 870 } 871 }