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