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