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