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