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.relation.sort.RelationSorter; 038import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; 039import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; 040import org.openstreetmap.josm.gui.layer.OsmDataLayer; 041import org.openstreetmap.josm.gui.tagging.PresetHandler; 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 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, PresetHandler presetHandler) { 066 members = new ArrayList<>(); 067 listeners = new CopyOnWriteArrayList<>(); 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 // fix #10524 - IndexOutOfBoundsException: Index: 2, Size: 2 208 if (rowIndex >= members.size()) { 209 return; 210 } 211 RelationMember member = members.get(rowIndex); 212 RelationMember newMember = new RelationMember(value.toString(), member.getMember()); 213 members.remove(rowIndex); 214 members.add(rowIndex, newMember); 215 } 216 217 @Override 218 public OsmPrimitive getReferredPrimitive(int idx) { 219 return members.get(idx).getMember(); 220 } 221 222 public void moveUp(int[] selectedRows) { 223 if (!canMoveUp(selectedRows)) 224 return; 225 226 for (int row : selectedRows) { 227 RelationMember member1 = members.get(row); 228 RelationMember member2 = members.get(row - 1); 229 members.set(row, member2); 230 members.set(row - 1, member1); 231 } 232 fireTableDataChanged(); 233 getSelectionModel().setValueIsAdjusting(true); 234 getSelectionModel().clearSelection(); 235 for (int row : selectedRows) { 236 row--; 237 getSelectionModel().addSelectionInterval(row, row); 238 } 239 getSelectionModel().setValueIsAdjusting(false); 240 fireMakeMemberVisible(selectedRows[0] - 1); 241 } 242 243 public void moveDown(int[] selectedRows) { 244 if (!canMoveDown(selectedRows)) 245 return; 246 247 for (int i = selectedRows.length - 1; i >= 0; i--) { 248 int row = selectedRows[i]; 249 RelationMember member1 = members.get(row); 250 RelationMember member2 = members.get(row + 1); 251 members.set(row, member2); 252 members.set(row + 1, member1); 253 } 254 fireTableDataChanged(); 255 getSelectionModel(); 256 getSelectionModel().setValueIsAdjusting(true); 257 getSelectionModel().clearSelection(); 258 for (int row : selectedRows) { 259 row++; 260 getSelectionModel().addSelectionInterval(row, row); 261 } 262 getSelectionModel().setValueIsAdjusting(false); 263 fireMakeMemberVisible(selectedRows[0] + 1); 264 } 265 266 public void remove(int[] selectedRows) { 267 if (!canRemove(selectedRows)) 268 return; 269 int offset = 0; 270 for (int row : selectedRows) { 271 row -= offset; 272 if (members.size() > row) { 273 members.remove(row); 274 offset++; 275 } 276 } 277 fireTableDataChanged(); 278 } 279 280 public boolean canMoveUp(int[] rows) { 281 if (rows == null || rows.length == 0) 282 return false; 283 Arrays.sort(rows); 284 return rows[0] > 0 && members.size() > 0; 285 } 286 287 public boolean canMoveDown(int[] rows) { 288 if (rows == null || rows.length == 0) 289 return false; 290 Arrays.sort(rows); 291 return members.size() > 0 && rows[rows.length - 1] < members.size() - 1; 292 } 293 294 public boolean canRemove(int[] rows) { 295 if (rows == null || rows.length == 0) 296 return false; 297 return true; 298 } 299 300 public DefaultListSelectionModel getSelectionModel() { 301 if (listSelectionModel == null) { 302 listSelectionModel = new DefaultListSelectionModel(); 303 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 304 } 305 return listSelectionModel; 306 } 307 308 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) { 309 if (primitives == null) 310 return; 311 Iterator<RelationMember> it = members.iterator(); 312 while (it.hasNext()) { 313 RelationMember member = it.next(); 314 if (primitives.contains(member.getMember())) { 315 it.remove(); 316 } 317 } 318 fireTableDataChanged(); 319 } 320 321 public void applyToRelation(Relation relation) { 322 relation.setMembers(members); 323 } 324 325 public boolean hasSameMembersAs(Relation relation) { 326 if (relation == null) 327 return false; 328 if (relation.getMembersCount() != members.size()) 329 return false; 330 for (int i = 0; i < relation.getMembersCount(); i++) { 331 if (!relation.getMember(i).equals(members.get(i))) 332 return false; 333 } 334 return true; 335 } 336 337 /** 338 * Replies the set of incomplete primitives 339 * 340 * @return the set of incomplete primitives 341 */ 342 public Set<OsmPrimitive> getIncompleteMemberPrimitives() { 343 Set<OsmPrimitive> ret = new HashSet<>(); 344 for (RelationMember member : members) { 345 if (member.getMember().isIncomplete()) { 346 ret.add(member.getMember()); 347 } 348 } 349 return ret; 350 } 351 352 /** 353 * Replies the set of selected incomplete primitives 354 * 355 * @return the set of selected incomplete primitives 356 */ 357 public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() { 358 Set<OsmPrimitive> ret = new HashSet<>(); 359 for (RelationMember member : getSelectedMembers()) { 360 if (member.getMember().isIncomplete()) { 361 ret.add(member.getMember()); 362 } 363 } 364 return ret; 365 } 366 367 /** 368 * Replies true if at least one the relation members is incomplete 369 * 370 * @return true if at least one the relation members is incomplete 371 */ 372 public boolean hasIncompleteMembers() { 373 for (RelationMember member : members) { 374 if (member.getMember().isIncomplete()) 375 return true; 376 } 377 return false; 378 } 379 380 /** 381 * Replies true if at least one of the selected members is incomplete 382 * 383 * @return true if at least one of the selected members is incomplete 384 */ 385 public boolean hasIncompleteSelectedMembers() { 386 for (RelationMember member : getSelectedMembers()) { 387 if (member.getMember().isIncomplete()) 388 return true; 389 } 390 return false; 391 } 392 393 protected List<Integer> getSelectedIndices() { 394 List<Integer> selectedIndices = new ArrayList<>(); 395 for (int i = 0; i < members.size(); i++) { 396 if (getSelectionModel().isSelectedIndex(i)) { 397 selectedIndices.add(i); 398 } 399 } 400 return selectedIndices; 401 } 402 403 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) { 404 final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPresetType.RELATION), presetHandler.getSelection().iterator().next().getKeys(), false); 405 if (primitives == null) 406 return; 407 int idx = index; 408 for (OsmPrimitive primitive : primitives) { 409 Set<String> potentialRoles = new TreeSet<>(); 410 for (TaggingPreset tp : presets) { 411 String suggestedRole = tp.suggestRoleForOsmPrimitive(primitive); 412 if (suggestedRole != null) { 413 potentialRoles.add(suggestedRole); 414 } 415 } 416 // TODO: propose user to choose role among potential ones instead of picking first one 417 final String role = potentialRoles.isEmpty() ? null : potentialRoles.iterator().next(); 418 RelationMember member = new RelationMember(role == null ? "" : role, primitive); 419 members.add(idx++, member); 420 } 421 fireTableDataChanged(); 422 getSelectionModel().clearSelection(); 423 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1); 424 fireMakeMemberVisible(index); 425 } 426 427 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) { 428 addMembersAtIndex(primitives, 0); 429 } 430 431 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) { 432 addMembersAtIndex(primitives, members.size()); 433 } 434 435 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) { 436 addMembersAtIndex(primitives, idx); 437 } 438 439 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) { 440 addMembersAtIndex(primitives, idx + 1); 441 } 442 443 /** 444 * Replies the number of members which refer to a particular primitive 445 * 446 * @param primitive the primitive 447 * @return the number of members which refer to a particular primitive 448 */ 449 public int getNumMembersWithPrimitive(OsmPrimitive primitive) { 450 int count = 0; 451 for (RelationMember member : members) { 452 if (member.getMember().equals(primitive)) { 453 count++; 454 } 455 } 456 return count; 457 } 458 459 /** 460 * updates the role of the members given by the indices in <code>idx</code> 461 * 462 * @param idx the array of indices 463 * @param role the new role 464 */ 465 public void updateRole(int[] idx, String role) { 466 if (idx == null || idx.length == 0) 467 return; 468 for (int row : idx) { 469 // fix #7885 - IndexOutOfBoundsException: Index: 39, Size: 39 470 if (row >= members.size()) { 471 continue; 472 } 473 RelationMember oldMember = members.get(row); 474 RelationMember newMember = new RelationMember(role, oldMember.getMember()); 475 members.remove(row); 476 members.add(row, newMember); 477 } 478 fireTableDataChanged(); 479 for (int row : idx) { 480 getSelectionModel().addSelectionInterval(row, row); 481 } 482 } 483 484 /** 485 * Get the currently selected relation members 486 * 487 * @return a collection with the currently selected relation members 488 */ 489 public Collection<RelationMember> getSelectedMembers() { 490 List<RelationMember> selectedMembers = new ArrayList<>(); 491 for (int i : getSelectedIndices()) { 492 selectedMembers.add(members.get(i)); 493 } 494 return selectedMembers; 495 } 496 497 /** 498 * Replies the set of selected referers. Never null, but may be empty. 499 * 500 * @return the set of selected referers 501 */ 502 public Collection<OsmPrimitive> getSelectedChildPrimitives() { 503 Collection<OsmPrimitive> ret = new ArrayList<>(); 504 for (RelationMember m: getSelectedMembers()) { 505 ret.add(m.getMember()); 506 } 507 return ret; 508 } 509 510 /** 511 * Replies the set of selected referers. Never null, but may be empty. 512 * 513 * @return the set of selected referers 514 */ 515 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) { 516 HashSet<OsmPrimitive> ret = new HashSet<>(); 517 if (referenceSet == null) return null; 518 for (RelationMember m: members) { 519 if (referenceSet.contains(m.getMember())) { 520 ret.add(m.getMember()); 521 } 522 } 523 return ret; 524 } 525 526 /** 527 * Selects the members in the collection selectedMembers 528 * 529 * @param selectedMembers the collection of selected members 530 */ 531 public void setSelectedMembers(Collection<RelationMember> selectedMembers) { 532 if (selectedMembers == null || selectedMembers.isEmpty()) { 533 getSelectionModel().clearSelection(); 534 return; 535 } 536 537 // lookup the indices for the respective members 538 // 539 Set<Integer> selectedIndices = new HashSet<>(); 540 for (RelationMember member : selectedMembers) { 541 for (int idx = 0; idx < members.size(); ++idx) { 542 if (member.equals(members.get(idx))) { 543 selectedIndices.add(idx); 544 } 545 } 546 } 547 setSelectedMembersIdx(selectedIndices); 548 } 549 550 /** 551 * Selects the members in the collection selectedIndices 552 * 553 * @param selectedIndices the collection of selected member indices 554 */ 555 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) { 556 if (selectedIndices == null || selectedIndices.isEmpty()) { 557 getSelectionModel().clearSelection(); 558 return; 559 } 560 // select the members 561 // 562 getSelectionModel().setValueIsAdjusting(true); 563 getSelectionModel().clearSelection(); 564 for (int row : selectedIndices) { 565 getSelectionModel().addSelectionInterval(row, row); 566 } 567 getSelectionModel().setValueIsAdjusting(false); 568 // make the first selected member visible 569 // 570 if (!selectedIndices.isEmpty()) { 571 fireMakeMemberVisible(Collections.min(selectedIndices)); 572 } 573 } 574 575 /** 576 * Replies true if the index-th relation members referrs 577 * to an editable relation, i.e. a relation which is not 578 * incomplete. 579 * 580 * @param index the index 581 * @return true, if the index-th relation members referrs 582 * to an editable relation, i.e. a relation which is not 583 * incomplete 584 */ 585 public boolean isEditableRelation(int index) { 586 if (index < 0 || index >= members.size()) 587 return false; 588 RelationMember member = members.get(index); 589 if (!member.isRelation()) 590 return false; 591 Relation r = member.getRelation(); 592 return !r.isIncomplete(); 593 } 594 595 /** 596 * Replies true if there is at least one relation member given as {@code members} 597 * which refers to at least on the primitives in {@code primitives}. 598 * 599 * @param members the members 600 * @param primitives the collection of primitives 601 * @return true if there is at least one relation member in this model 602 * which refers to at least on the primitives in <code>primitives</code>; false 603 * otherwise 604 */ 605 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) { 606 if (primitives == null || primitives.isEmpty()) 607 return false; 608 HashSet<OsmPrimitive> referrers = new HashSet<>(); 609 for (RelationMember member : members) { 610 referrers.add(member.getMember()); 611 } 612 for (OsmPrimitive referred : primitives) { 613 if (referrers.contains(referred)) 614 return true; 615 } 616 return false; 617 } 618 619 /** 620 * Replies true if there is at least one relation member in this model 621 * which refers to at least on the primitives in <code>primitives</code>. 622 * 623 * @param primitives the collection of primitives 624 * @return true if there is at least one relation member in this model 625 * which refers to at least on the primitives in <code>primitives</code>; false 626 * otherwise 627 */ 628 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) { 629 return hasMembersReferringTo(members, primitives); 630 } 631 632 /** 633 * Selects all mebers which refer to {@link OsmPrimitive}s in the collections 634 * <code>primitmives</code>. Does nothing is primitives is null. 635 * 636 * @param primitives the collection of primitives 637 */ 638 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) { 639 if (primitives == null) return; 640 getSelectionModel().setValueIsAdjusting(true); 641 getSelectionModel().clearSelection(); 642 for (int i=0; i< members.size();i++) { 643 RelationMember m = members.get(i); 644 if (primitives.contains(m.getMember())) { 645 this.getSelectionModel().addSelectionInterval(i,i); 646 } 647 } 648 getSelectionModel().setValueIsAdjusting(false); 649 if (!getSelectedIndices().isEmpty()) { 650 fireMakeMemberVisible(getSelectedIndices().get(0)); 651 } 652 } 653 654 /** 655 * Replies true if <code>primitive</code> is currently selected in the layer this 656 * model is attached to 657 * 658 * @param primitive the primitive 659 * @return true if <code>primitive</code> is currently selected in the layer this 660 * model is attached to, false otherwise 661 */ 662 public boolean isInJosmSelection(OsmPrimitive primitive) { 663 return layer.data.isSelected(primitive); 664 } 665 666 /** 667 * Replies true if the layer this model belongs to is equal to the active 668 * layer 669 * 670 * @return true if the layer this model belongs to is equal to the active 671 * layer 672 */ 673 protected boolean isActiveLayer() { 674 if (!Main.isDisplayingMapView()) return false; 675 return Main.map.mapView.getActiveLayer() == layer; 676 } 677 678 /** 679 * Sort the selected relation members by the way they are linked. 680 */ 681 void sort() { 682 List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers()); 683 List<RelationMember> sortedMembers = null; 684 List<RelationMember> newMembers; 685 if (selectedMembers.size() <= 1) { 686 newMembers = relationSorter.sortMembers(members); 687 sortedMembers = newMembers; 688 } else { 689 sortedMembers = relationSorter.sortMembers(selectedMembers); 690 List<Integer> selectedIndices = getSelectedIndices(); 691 newMembers = new ArrayList<>(); 692 boolean inserted = false; 693 for (int i=0; i < members.size(); i++) { 694 if (selectedIndices.contains(i)) { 695 if (!inserted) { 696 newMembers.addAll(sortedMembers); 697 inserted = true; 698 } 699 } else { 700 newMembers.add(members.get(i)); 701 } 702 } 703 } 704 705 if (members.size() != newMembers.size()) throw new AssertionError(); 706 707 members.clear(); 708 members.addAll(newMembers); 709 fireTableDataChanged(); 710 setSelectedMembers(sortedMembers); 711 } 712 713 714 WayConnectionType getWayConnection(int i) { 715 if (connectionType == null) { 716 connectionType = wayConnectionTypeCalculator.updateLinks(members); 717 } 718 return connectionType.get(i); 719 } 720 721 @Override 722 public void tableChanged(TableModelEvent e) { 723 connectionType = null; 724 } 725 726 /** 727 * Reverse the relation members. 728 */ 729 void reverse() { 730 List<Integer> selectedIndices = getSelectedIndices(); 731 List<Integer> selectedIndicesReversed = getSelectedIndices(); 732 733 if (selectedIndices.size() <= 1) { 734 Collections.reverse(members); 735 fireTableDataChanged(); 736 setSelectedMembers(members); 737 } else { 738 Collections.reverse(selectedIndicesReversed); 739 740 List<RelationMember> newMembers = new ArrayList<>(members); 741 742 for (int i=0; i < selectedIndices.size(); i++) { 743 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i))); 744 } 745 746 if (members.size() != newMembers.size()) throw new AssertionError(); 747 members.clear(); 748 members.addAll(newMembers); 749 fireTableDataChanged(); 750 setSelectedMembersIdx(selectedIndices); 751 } 752 } 753}