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