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