001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.EnumSet; 009import java.util.HashSet; 010import java.util.Iterator; 011import java.util.List; 012import java.util.Set; 013import java.util.TreeSet; 014import java.util.concurrent.CopyOnWriteArrayList; 015 016import javax.swing.DefaultListSelectionModel; 017import javax.swing.ListSelectionModel; 018import javax.swing.event.TableModelEvent; 019import javax.swing.event.TableModelListener; 020import javax.swing.table.AbstractTableModel; 021 022import org.openstreetmap.josm.Main; 023import org.openstreetmap.josm.data.SelectionChangedListener; 024import org.openstreetmap.josm.data.osm.DataSet; 025import org.openstreetmap.josm.data.osm.OsmPrimitive; 026import org.openstreetmap.josm.data.osm.Relation; 027import org.openstreetmap.josm.data.osm.RelationMember; 028import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 029import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 030import org.openstreetmap.josm.data.osm.event.DataSetListener; 031import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 032import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 033import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 034import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 035import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 036import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 037import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel; 038import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter; 039import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; 040import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; 041import org.openstreetmap.josm.gui.layer.OsmDataLayer; 042import org.openstreetmap.josm.gui.tagging.TaggingPreset; 043import org.openstreetmap.josm.gui.tagging.TaggingPresetType; 044import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel; 045 046public class MemberTableModel extends AbstractTableModel implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPrimitivesTableModel { 047 048 /** 049 * data of the table model: The list of members and the cached WayConnectionType of each member. 050 **/ 051 private List<RelationMember> members; 052 private List<WayConnectionType> connectionType = null; 053 054 private DefaultListSelectionModel listSelectionModel; 055 private final CopyOnWriteArrayList<IMemberModelListener> listeners; 056 private final OsmDataLayer layer; 057 private final PresetListPanel.PresetHandler presetHandler; 058 059 private final WayConnectionTypeCalculator wayConnectionTypeCalculator = new WayConnectionTypeCalculator(); 060 private final RelationSorter relationSorter = new RelationSorter(); 061 062 /** 063 * constructor 064 */ 065 public MemberTableModel(OsmDataLayer layer, PresetListPanel.PresetHandler presetHandler) { 066 members = new ArrayList<RelationMember>(); 067 listeners = new CopyOnWriteArrayList<IMemberModelListener>(); 068 this.layer = layer; 069 this.presetHandler = presetHandler; 070 addTableModelListener(this); 071 } 072 073 public OsmDataLayer getLayer() { 074 return layer; 075 } 076 077 public void register() { 078 DataSet.addSelectionListener(this); 079 getLayer().data.addDataSetListener(this); 080 } 081 082 public void unregister() { 083 DataSet.removeSelectionListener(this); 084 getLayer().data.removeDataSetListener(this); 085 } 086 087 /* --------------------------------------------------------------------------- */ 088 /* Interface SelectionChangedListener */ 089 /* --------------------------------------------------------------------------- */ 090 @Override 091 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 092 if (Main.main.getEditLayer() != this.layer) return; 093 // just trigger a repaint 094 Collection<RelationMember> sel = getSelectedMembers(); 095 fireTableDataChanged(); 096 setSelectedMembers(sel); 097 } 098 099 /* --------------------------------------------------------------------------- */ 100 /* Interface DataSetListener */ 101 /* --------------------------------------------------------------------------- */ 102 @Override 103 public void dataChanged(DataChangedEvent event) { 104 // just trigger a repaint - the display name of the relation members may 105 // have changed 106 Collection<RelationMember> sel = getSelectedMembers(); 107 fireTableDataChanged(); 108 setSelectedMembers(sel); 109 } 110 111 @Override 112 public void nodeMoved(NodeMovedEvent event) {/* ignore */} 113 @Override 114 public void primitivesAdded(PrimitivesAddedEvent event) {/* ignore */} 115 116 @Override 117 public void primitivesRemoved(PrimitivesRemovedEvent event) { 118 // ignore - the relation in the editor might become out of sync with the relation 119 // in the dataset. We will deal with it when the relation editor is closed or 120 // when the changes in the editor are applied. 121 } 122 123 @Override 124 public void relationMembersChanged(RelationMembersChangedEvent event) { 125 // ignore - the relation in the editor might become out of sync with the relation 126 // in the dataset. We will deal with it when the relation editor is closed or 127 // when the changes in the editor are applied. 128 } 129 130 @Override 131 public void tagsChanged(TagsChangedEvent event) { 132 // just refresh the respective table cells 133 // 134 Collection<RelationMember> sel = getSelectedMembers(); 135 for (int i=0; i < members.size();i++) { 136 if (members.get(i).getMember() == event.getPrimitive()) { 137 fireTableCellUpdated(i, 1 /* the column with the primitive name */); 138 } 139 } 140 setSelectedMembers(sel); 141 } 142 143 @Override 144 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignore */} 145 146 @Override 147 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */} 148 /* --------------------------------------------------------------------------- */ 149 150 public void addMemberModelListener(IMemberModelListener listener) { 151 if (listener != null) { 152 listeners.addIfAbsent(listener); 153 } 154 } 155 156 public void removeMemberModelListener(IMemberModelListener listener) { 157 listeners.remove(listener); 158 } 159 160 protected void fireMakeMemberVisible(int index) { 161 for (IMemberModelListener listener : listeners) { 162 listener.makeMemberVisible(index); 163 } 164 } 165 166 public void populate(Relation relation) { 167 members.clear(); 168 if (relation != null) { 169 // make sure we work with clones of the relation members 170 // in the model. 171 members.addAll(new Relation(relation).getMembers()); 172 } 173 fireTableDataChanged(); 174 } 175 176 @Override 177 public int getColumnCount() { 178 return 3; 179 } 180 181 @Override 182 public int getRowCount() { 183 return members.size(); 184 } 185 186 @Override 187 public Object getValueAt(int rowIndex, int columnIndex) { 188 switch (columnIndex) { 189 case 0: 190 return members.get(rowIndex).getRole(); 191 case 1: 192 return members.get(rowIndex).getMember(); 193 case 2: 194 return getWayConnection(rowIndex); 195 } 196 // should not happen 197 return null; 198 } 199 200 @Override 201 public boolean isCellEditable(int rowIndex, int columnIndex) { 202 return columnIndex == 0; 203 } 204 205 @Override 206 public void setValueAt(Object value, int rowIndex, int columnIndex) { 207 RelationMember member = members.get(rowIndex); 208 RelationMember newMember = new RelationMember(value.toString(), member.getMember()); 209 members.remove(rowIndex); 210 members.add(rowIndex, newMember); 211 } 212 213 @Override 214 public OsmPrimitive getReferredPrimitive(int idx) { 215 return members.get(idx).getMember(); 216 } 217 218 public void moveUp(int[] selectedRows) { 219 if (!canMoveUp(selectedRows)) 220 return; 221 222 for (int row : selectedRows) { 223 RelationMember member1 = members.get(row); 224 RelationMember member2 = members.get(row - 1); 225 members.set(row, member2); 226 members.set(row - 1, member1); 227 } 228 fireTableDataChanged(); 229 getSelectionModel().setValueIsAdjusting(true); 230 getSelectionModel().clearSelection(); 231 for (int row : selectedRows) { 232 row--; 233 getSelectionModel().addSelectionInterval(row, row); 234 } 235 getSelectionModel().setValueIsAdjusting(false); 236 fireMakeMemberVisible(selectedRows[0] - 1); 237 } 238 239 public void moveDown(int[] selectedRows) { 240 if (!canMoveDown(selectedRows)) 241 return; 242 243 for (int i = selectedRows.length - 1; i >= 0; i--) { 244 int row = selectedRows[i]; 245 RelationMember member1 = members.get(row); 246 RelationMember member2 = members.get(row + 1); 247 members.set(row, member2); 248 members.set(row + 1, member1); 249 } 250 fireTableDataChanged(); 251 getSelectionModel(); 252 getSelectionModel().setValueIsAdjusting(true); 253 getSelectionModel().clearSelection(); 254 for (int row : selectedRows) { 255 row++; 256 getSelectionModel().addSelectionInterval(row, row); 257 } 258 getSelectionModel().setValueIsAdjusting(false); 259 fireMakeMemberVisible(selectedRows[0] + 1); 260 } 261 262 public void remove(int[] selectedRows) { 263 if (!canRemove(selectedRows)) 264 return; 265 int offset = 0; 266 for (int row : selectedRows) { 267 row -= offset; 268 if (members.size() > row) { 269 members.remove(row); 270 offset++; 271 } 272 } 273 fireTableDataChanged(); 274 } 275 276 public boolean canMoveUp(int[] rows) { 277 if (rows == null || rows.length == 0) 278 return false; 279 Arrays.sort(rows); 280 return rows[0] > 0 && members.size() > 0; 281 } 282 283 public boolean canMoveDown(int[] rows) { 284 if (rows == null || rows.length == 0) 285 return false; 286 Arrays.sort(rows); 287 return members.size() > 0 && rows[rows.length - 1] < members.size() - 1; 288 } 289 290 public boolean canRemove(int[] rows) { 291 if (rows == null || rows.length == 0) 292 return false; 293 return true; 294 } 295 296 public DefaultListSelectionModel getSelectionModel() { 297 if (listSelectionModel == null) { 298 listSelectionModel = new DefaultListSelectionModel(); 299 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 300 } 301 return listSelectionModel; 302 } 303 304 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) { 305 if (primitives == null) 306 return; 307 Iterator<RelationMember> it = members.iterator(); 308 while (it.hasNext()) { 309 RelationMember member = it.next(); 310 if (primitives.contains(member.getMember())) { 311 it.remove(); 312 } 313 } 314 fireTableDataChanged(); 315 } 316 317 public void applyToRelation(Relation relation) { 318 relation.setMembers(members); 319 } 320 321 public boolean hasSameMembersAs(Relation relation) { 322 if (relation == null) 323 return false; 324 if (relation.getMembersCount() != members.size()) 325 return false; 326 for (int i = 0; i < relation.getMembersCount(); i++) { 327 if (!relation.getMember(i).equals(members.get(i))) 328 return false; 329 } 330 return true; 331 } 332 333 /** 334 * Replies the set of incomplete primitives 335 * 336 * @return the set of incomplete primitives 337 */ 338 public Set<OsmPrimitive> getIncompleteMemberPrimitives() { 339 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 340 for (RelationMember member : members) { 341 if (member.getMember().isIncomplete()) { 342 ret.add(member.getMember()); 343 } 344 } 345 return ret; 346 } 347 348 /** 349 * Replies the set of selected incomplete primitives 350 * 351 * @return the set of selected incomplete primitives 352 */ 353 public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() { 354 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 355 for (RelationMember member : getSelectedMembers()) { 356 if (member.getMember().isIncomplete()) { 357 ret.add(member.getMember()); 358 } 359 } 360 return ret; 361 } 362 363 /** 364 * Replies true if at least one the relation members is incomplete 365 * 366 * @return true if at least one the relation members is incomplete 367 */ 368 public boolean hasIncompleteMembers() { 369 for (RelationMember member : members) { 370 if (member.getMember().isIncomplete()) 371 return true; 372 } 373 return false; 374 } 375 376 /** 377 * Replies true if at least one of the selected members is incomplete 378 * 379 * @return true if at least one of the selected members is incomplete 380 */ 381 public boolean hasIncompleteSelectedMembers() { 382 for (RelationMember member : getSelectedMembers()) { 383 if (member.getMember().isIncomplete()) 384 return true; 385 } 386 return false; 387 } 388 389 protected List<Integer> getSelectedIndices() { 390 List<Integer> selectedIndices = new ArrayList<Integer>(); 391 for (int i = 0; i < members.size(); i++) { 392 if (getSelectionModel().isSelectedIndex(i)) { 393 selectedIndices.add(i); 394 } 395 } 396 return selectedIndices; 397 } 398 399 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) { 400 final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPresetType.RELATION), presetHandler.getSelection().iterator().next().getKeys(), false); 401 if (primitives == null) 402 return; 403 int idx = index; 404 for (OsmPrimitive primitive : primitives) { 405 Set<String> potentialRoles = new TreeSet<String>(); 406 for (TaggingPreset tp : presets) { 407 String suggestedRole = tp.suggestRoleForOsmPrimitive(primitive); 408 if (suggestedRole != null) { 409 potentialRoles.add(suggestedRole); 410 } 411 } 412 // TODO: propose user to choose role among potential ones instead of picking first one 413 final String role = potentialRoles.isEmpty() ? null : potentialRoles.iterator().next(); 414 RelationMember member = new RelationMember(role == null ? "" : role, primitive); 415 members.add(idx++, member); 416 } 417 fireTableDataChanged(); 418 getSelectionModel().clearSelection(); 419 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1); 420 fireMakeMemberVisible(index); 421 } 422 423 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) { 424 addMembersAtIndex(primitives, 0); 425 } 426 427 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) { 428 addMembersAtIndex(primitives, members.size()); 429 } 430 431 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) { 432 addMembersAtIndex(primitives, idx); 433 } 434 435 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) { 436 addMembersAtIndex(primitives, idx + 1); 437 } 438 439 /** 440 * Replies the number of members which refer to a particular primitive 441 * 442 * @param primitive the primitive 443 * @return the number of members which refer to a particular primitive 444 */ 445 public int getNumMembersWithPrimitive(OsmPrimitive primitive) { 446 int count = 0; 447 for (RelationMember member : members) { 448 if (member.getMember().equals(primitive)) { 449 count++; 450 } 451 } 452 return count; 453 } 454 455 /** 456 * updates the role of the members given by the indices in <code>idx</code> 457 * 458 * @param idx the array of indices 459 * @param role the new role 460 */ 461 public void updateRole(int[] idx, String role) { 462 if (idx == null || idx.length == 0) 463 return; 464 for (int row : idx) { 465 RelationMember oldMember = members.get(row); 466 RelationMember newMember = new RelationMember(role, oldMember.getMember()); 467 members.remove(row); 468 members.add(row, newMember); 469 } 470 fireTableDataChanged(); 471 for (int row : idx) { 472 getSelectionModel().addSelectionInterval(row, row); 473 } 474 } 475 476 /** 477 * Get the currently selected relation members 478 * 479 * @return a collection with the currently selected relation members 480 */ 481 public Collection<RelationMember> getSelectedMembers() { 482 List<RelationMember> selectedMembers = new ArrayList<RelationMember>(); 483 for (int i : getSelectedIndices()) { 484 selectedMembers.add(members.get(i)); 485 } 486 return selectedMembers; 487 } 488 489 /** 490 * Replies the set of selected referers. Never null, but may be empty. 491 * 492 * @return the set of selected referers 493 */ 494 public Collection<OsmPrimitive> getSelectedChildPrimitives() { 495 Collection<OsmPrimitive> ret = new ArrayList<OsmPrimitive>(); 496 for (RelationMember m: getSelectedMembers()) { 497 ret.add(m.getMember()); 498 } 499 return ret; 500 } 501 502 /** 503 * Replies the set of selected referers. Never null, but may be empty. 504 * 505 * @return the set of selected referers 506 */ 507 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) { 508 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 509 if (referenceSet == null) return null; 510 for (RelationMember m: members) { 511 if (referenceSet.contains(m.getMember())) { 512 ret.add(m.getMember()); 513 } 514 } 515 return ret; 516 } 517 518 /** 519 * Selects the members in the collection selectedMembers 520 * 521 * @param selectedMembers the collection of selected members 522 */ 523 public void setSelectedMembers(Collection<RelationMember> selectedMembers) { 524 if (selectedMembers == null || selectedMembers.isEmpty()) { 525 getSelectionModel().clearSelection(); 526 return; 527 } 528 529 // lookup the indices for the respective members 530 // 531 Set<Integer> selectedIndices = new HashSet<Integer>(); 532 for (RelationMember member : selectedMembers) { 533 for (int idx = 0; idx < members.size(); ++idx) { 534 if (member.equals(members.get(idx))) { 535 selectedIndices.add(idx); 536 } 537 } 538 } 539 setSelectedMembersIdx(selectedIndices); 540 } 541 542 /** 543 * Selects the members in the collection selectedIndices 544 * 545 * @param selectedIndices the collection of selected member indices 546 */ 547 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) { 548 if (selectedIndices == null || selectedIndices.isEmpty()) { 549 getSelectionModel().clearSelection(); 550 return; 551 } 552 // select the members 553 // 554 getSelectionModel().setValueIsAdjusting(true); 555 getSelectionModel().clearSelection(); 556 for (int row : selectedIndices) { 557 getSelectionModel().addSelectionInterval(row, row); 558 } 559 getSelectionModel().setValueIsAdjusting(false); 560 // make the first selected member visible 561 // 562 if (!selectedIndices.isEmpty()) { 563 fireMakeMemberVisible(Collections.min(selectedIndices)); 564 } 565 } 566 567 /** 568 * Replies true if the index-th relation members referrs 569 * to an editable relation, i.e. a relation which is not 570 * incomplete. 571 * 572 * @param index the index 573 * @return true, if the index-th relation members referrs 574 * to an editable relation, i.e. a relation which is not 575 * incomplete 576 */ 577 public boolean isEditableRelation(int index) { 578 if (index < 0 || index >= members.size()) 579 return false; 580 RelationMember member = members.get(index); 581 if (!member.isRelation()) 582 return false; 583 Relation r = member.getRelation(); 584 return !r.isIncomplete(); 585 } 586 587 /** 588 * Replies true if there is at least one relation member given as {@code members} 589 * which refers to at least on the primitives in {@code primitives}. 590 * 591 * @param members the members 592 * @param primitives the collection of primitives 593 * @return true if there is at least one relation member in this model 594 * which refers to at least on the primitives in <code>primitives</code>; false 595 * otherwise 596 */ 597 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) { 598 if (primitives == null || primitives.isEmpty()) 599 return false; 600 HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>(); 601 for (RelationMember member : members) { 602 referrers.add(member.getMember()); 603 } 604 for (OsmPrimitive referred : primitives) { 605 if (referrers.contains(referred)) 606 return true; 607 } 608 return false; 609 } 610 611 /** 612 * Replies true if there is at least one relation member in this model 613 * which refers to at least on the primitives in <code>primitives</code>. 614 * 615 * @param primitives the collection of primitives 616 * @return true if there is at least one relation member in this model 617 * which refers to at least on the primitives in <code>primitives</code>; false 618 * otherwise 619 */ 620 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) { 621 return hasMembersReferringTo(members, primitives); 622 } 623 624 /** 625 * Selects all mebers which refer to {@link OsmPrimitive}s in the collections 626 * <code>primitmives</code>. Does nothing is primitives is null. 627 * 628 * @param primitives the collection of primitives 629 */ 630 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) { 631 if (primitives == null) return; 632 getSelectionModel().setValueIsAdjusting(true); 633 getSelectionModel().clearSelection(); 634 for (int i=0; i< members.size();i++) { 635 RelationMember m = members.get(i); 636 if (primitives.contains(m.getMember())) { 637 this.getSelectionModel().addSelectionInterval(i,i); 638 } 639 } 640 getSelectionModel().setValueIsAdjusting(false); 641 if (!getSelectedIndices().isEmpty()) { 642 fireMakeMemberVisible(getSelectedIndices().get(0)); 643 } 644 } 645 646 /** 647 * Replies true if <code>primitive</code> is currently selected in the layer this 648 * model is attached to 649 * 650 * @param primitive the primitive 651 * @return true if <code>primitive</code> is currently selected in the layer this 652 * model is attached to, false otherwise 653 */ 654 public boolean isInJosmSelection(OsmPrimitive primitive) { 655 return layer.data.isSelected(primitive); 656 } 657 658 /** 659 * Replies true if the layer this model belongs to is equal to the active 660 * layer 661 * 662 * @return true if the layer this model belongs to is equal to the active 663 * layer 664 */ 665 protected boolean isActiveLayer() { 666 if (!Main.isDisplayingMapView()) return false; 667 return Main.map.mapView.getActiveLayer() == layer; 668 } 669 670 /** 671 * Sort the selected relation members by the way they are linked. 672 */ 673 void sort() { 674 List<RelationMember> selectedMembers = new ArrayList<RelationMember>(getSelectedMembers()); 675 List<RelationMember> sortedMembers = null; 676 List<RelationMember> newMembers; 677 if (selectedMembers.size() <= 1) { 678 newMembers = relationSorter.sortMembers(members); 679 sortedMembers = newMembers; 680 } else { 681 sortedMembers = relationSorter.sortMembers(selectedMembers); 682 List<Integer> selectedIndices = getSelectedIndices(); 683 newMembers = new ArrayList<RelationMember>(); 684 boolean inserted = false; 685 for (int i=0; i < members.size(); i++) { 686 if (selectedIndices.contains(i)) { 687 if (!inserted) { 688 newMembers.addAll(sortedMembers); 689 inserted = true; 690 } 691 } else { 692 newMembers.add(members.get(i)); 693 } 694 } 695 } 696 697 if (members.size() != newMembers.size()) throw new AssertionError(); 698 699 members.clear(); 700 members.addAll(newMembers); 701 fireTableDataChanged(); 702 setSelectedMembers(sortedMembers); 703 } 704 705 706 WayConnectionType getWayConnection(int i) { 707 if (connectionType == null) { 708 connectionType = wayConnectionTypeCalculator.updateLinks(members); 709 } 710 return connectionType.get(i); 711 } 712 713 @Override 714 public void tableChanged(TableModelEvent e) { 715 connectionType = null; 716 } 717 718 /** 719 * Reverse the relation members. 720 */ 721 void reverse() { 722 List<Integer> selectedIndices = getSelectedIndices(); 723 List<Integer> selectedIndicesReversed = getSelectedIndices(); 724 725 if (selectedIndices.size() <= 1) { 726 Collections.reverse(members); 727 fireTableDataChanged(); 728 setSelectedMembers(members); 729 } else { 730 Collections.reverse(selectedIndicesReversed); 731 732 List<RelationMember> newMembers = new ArrayList<RelationMember>(members); 733 734 for (int i=0; i < selectedIndices.size(); i++) { 735 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i))); 736 } 737 738 if (members.size() != newMembers.size()) throw new AssertionError(); 739 members.clear(); 740 members.addAll(newMembers); 741 fireTableDataChanged(); 742 setSelectedMembersIdx(selectedIndices); 743 } 744 } 745}