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 final ListSelectionModel selectionModel = getSelectionModel(); 301 selectionModel.setValueIsAdjusting(true); 302 for (int row : selectedRows) { 303 row -= offset; 304 if (members.size() > row) { 305 members.remove(row); 306 selectionModel.removeIndexInterval(row, row); 307 offset++; 308 } 309 } 310 selectionModel.setValueIsAdjusting(false); 311 fireTableDataChanged(); 312 } 313 314 /** 315 * Checks that a range of rows can be removed. 316 * @param rows indexes of rows to remove 317 * @return {@code true} if rows can be removed 318 */ 319 public boolean canRemove(int... rows) { 320 return rows != null && rows.length != 0; 321 } 322 323 @Override 324 public DefaultListSelectionModel getSelectionModel() { 325 if (listSelectionModel == null) { 326 listSelectionModel = new DefaultListSelectionModel(); 327 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 328 } 329 return listSelectionModel; 330 } 331 332 @Override 333 public RelationMember getValue(int index) { 334 return members.get(index); 335 } 336 337 @Override 338 public RelationMember setValue(int index, RelationMember value) { 339 return members.set(index, value); 340 } 341 342 /** 343 * Remove members referring to the given list of primitives. 344 * @param primitives list of OSM primitives 345 */ 346 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) { 347 if (primitives == null) 348 return; 349 if (members.removeIf(member -> primitives.contains(member.getMember()))) 350 fireTableDataChanged(); 351 } 352 353 /** 354 * Applies this member model to the given relation. 355 * @param relation relation 356 */ 357 public void applyToRelation(Relation relation) { 358 relation.setMembers(members.stream() 359 .filter(rm -> !rm.getMember().isDeleted()).collect(Collectors.toList())); 360 } 361 362 /** 363 * Determines if this model has the same members as the given relation. 364 * @param relation relation 365 * @return {@code true} if this model has the same members as {@code relation} 366 */ 367 public boolean hasSameMembersAs(Relation relation) { 368 if (relation == null || relation.getMembersCount() != members.size()) 369 return false; 370 for (int i = 0; i < relation.getMembersCount(); i++) { 371 if (!relation.getMember(i).equals(members.get(i))) 372 return false; 373 } 374 return true; 375 } 376 377 /** 378 * Replies the set of incomplete primitives 379 * 380 * @return the set of incomplete primitives 381 */ 382 public Set<OsmPrimitive> getIncompleteMemberPrimitives() { 383 Set<OsmPrimitive> ret = new HashSet<>(); 384 for (RelationMember member : members) { 385 if (member.getMember().isIncomplete()) { 386 ret.add(member.getMember()); 387 } 388 } 389 return ret; 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 Set<OsmPrimitive> ret = new HashSet<>(); 399 for (RelationMember member : getSelectedMembers()) { 400 if (member.getMember().isIncomplete()) { 401 ret.add(member.getMember()); 402 } 403 } 404 return ret; 405 } 406 407 /** 408 * Replies true if at least one the relation members is incomplete 409 * 410 * @return true if at least one the relation members is incomplete 411 */ 412 public boolean hasIncompleteMembers() { 413 for (RelationMember member : members) { 414 if (member.getMember().isIncomplete()) 415 return true; 416 } 417 return false; 418 } 419 420 /** 421 * Replies true if at least one of the selected members is incomplete 422 * 423 * @return true if at least one of the selected members is incomplete 424 */ 425 public boolean hasIncompleteSelectedMembers() { 426 for (RelationMember member : getSelectedMembers()) { 427 if (member.getMember().isIncomplete()) 428 return true; 429 } 430 return false; 431 } 432 433 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) { 434 if (primitives == null || primitives.isEmpty()) 435 return; 436 int idx = index; 437 for (OsmPrimitive primitive : primitives) { 438 final RelationMember member = getRelationMemberForPrimitive(primitive); 439 members.add(idx++, member); 440 } 441 fireTableDataChanged(); 442 getSelectionModel().clearSelection(); 443 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1); 444 fireMakeMemberVisible(index); 445 } 446 447 RelationMember getRelationMemberForPrimitive(final OsmPrimitive primitive) { 448 final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets( 449 EnumSet.of(relation != null ? TaggingPresetType.forPrimitive(relation) : TaggingPresetType.RELATION), 450 presetHandler.getSelection().iterator().next().getKeys(), false); 451 Collection<String> potentialRoles = new TreeSet<>(); 452 for (TaggingPreset tp : presets) { 453 String suggestedRole = tp.suggestRoleForOsmPrimitive(primitive); 454 if (suggestedRole != null) { 455 potentialRoles.add(suggestedRole); 456 } 457 } 458 // TODO: propose user to choose role among potential ones instead of picking first one 459 final String role = potentialRoles.isEmpty() ? "" : potentialRoles.iterator().next(); 460 return new RelationMember(role == null ? "" : role, primitive); 461 } 462 463 void addMembersAtIndexKeepingOldSelection(final Iterable<RelationMember> newMembers, final int index) { 464 int idx = index; 465 for (RelationMember member : newMembers) { 466 members.add(idx++, member); 467 } 468 invalidateConnectionType(); 469 fireTableRowsInserted(index, idx - 1); 470 } 471 472 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) { 473 addMembersAtIndex(primitives, 0); 474 } 475 476 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) { 477 addMembersAtIndex(primitives, members.size()); 478 } 479 480 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) { 481 addMembersAtIndex(primitives, idx); 482 } 483 484 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) { 485 addMembersAtIndex(primitives, idx + 1); 486 } 487 488 /** 489 * Replies the number of members which refer to a particular primitive 490 * 491 * @param primitive the primitive 492 * @return the number of members which refer to a particular primitive 493 */ 494 public int getNumMembersWithPrimitive(OsmPrimitive primitive) { 495 int count = 0; 496 for (RelationMember member : members) { 497 if (member.getMember().equals(primitive)) { 498 count++; 499 } 500 } 501 return count; 502 } 503 504 /** 505 * updates the role of the members given by the indices in <code>idx</code> 506 * 507 * @param idx the array of indices 508 * @param role the new role 509 */ 510 public void updateRole(int[] idx, String role) { 511 if (idx == null || idx.length == 0) 512 return; 513 for (int row : idx) { 514 // fix #7885 - IndexOutOfBoundsException: Index: 39, Size: 39 515 if (row >= members.size()) { 516 continue; 517 } 518 RelationMember oldMember = members.get(row); 519 RelationMember newMember = new RelationMember(role, oldMember.getMember()); 520 members.remove(row); 521 members.add(row, newMember); 522 } 523 fireTableDataChanged(); 524 BitSet selected = new BitSet(); 525 for (int row : idx) { 526 selected.set(row); 527 } 528 addToSelectedMembers(selected); 529 } 530 531 /** 532 * Get the currently selected relation members 533 * 534 * @return a collection with the currently selected relation members 535 */ 536 public Collection<RelationMember> getSelectedMembers() { 537 List<RelationMember> selectedMembers = new ArrayList<>(); 538 for (int i : getSelectedIndices()) { 539 selectedMembers.add(members.get(i)); 540 } 541 return selectedMembers; 542 } 543 544 /** 545 * Replies the set of selected referers. Never null, but may be empty. 546 * 547 * @return the set of selected referers 548 */ 549 public Collection<OsmPrimitive> getSelectedChildPrimitives() { 550 Collection<OsmPrimitive> ret = new ArrayList<>(); 551 for (RelationMember m: getSelectedMembers()) { 552 ret.add(m.getMember()); 553 } 554 return ret; 555 } 556 557 /** 558 * Replies the set of selected referers. Never null, but may be empty. 559 * @param referenceSet reference set 560 * 561 * @return the set of selected referers 562 */ 563 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) { 564 Set<OsmPrimitive> ret = new HashSet<>(); 565 if (referenceSet == null) return null; 566 for (RelationMember m: members) { 567 if (referenceSet.contains(m.getMember())) { 568 ret.add(m.getMember()); 569 } 570 } 571 return ret; 572 } 573 574 /** 575 * Selects the members in the collection selectedMembers 576 * 577 * @param selectedMembers the collection of selected members 578 */ 579 public void setSelectedMembers(Collection<RelationMember> selectedMembers) { 580 if (selectedMembers == null || selectedMembers.isEmpty()) { 581 getSelectionModel().clearSelection(); 582 return; 583 } 584 585 // lookup the indices for the respective members 586 // 587 Set<Integer> selectedIndices = new HashSet<>(); 588 for (RelationMember member : selectedMembers) { 589 for (int idx = 0; idx < members.size(); ++idx) { 590 if (member.equals(members.get(idx))) { 591 selectedIndices.add(idx); 592 } 593 } 594 } 595 setSelectedMembersIdx(selectedIndices); 596 } 597 598 /** 599 * Selects the members in the collection selectedIndices 600 * 601 * @param selectedIndices the collection of selected member indices 602 */ 603 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) { 604 if (selectedIndices == null || selectedIndices.isEmpty()) { 605 getSelectionModel().clearSelection(); 606 return; 607 } 608 // select the members 609 // 610 getSelectionModel().setValueIsAdjusting(true); 611 getSelectionModel().clearSelection(); 612 BitSet selected = new BitSet(); 613 for (int row : selectedIndices) { 614 selected.set(row); 615 } 616 addToSelectedMembers(selected); 617 getSelectionModel().setValueIsAdjusting(false); 618 // make the first selected member visible 619 // 620 if (!selectedIndices.isEmpty()) { 621 fireMakeMemberVisible(Collections.min(selectedIndices)); 622 } 623 } 624 625 /** 626 * Add one or more members indices to the selection. 627 * Detect groups of consecutive indices. 628 * Only one costly call of addSelectionInterval is performed for each group 629 630 * @param selectedIndices selected indices as a bitset 631 * @return number of groups 632 */ 633 private int addToSelectedMembers(BitSet selectedIndices) { 634 if (selectedIndices == null || selectedIndices.isEmpty()) { 635 return 0; 636 } 637 // select the members 638 // 639 int start = selectedIndices.nextSetBit(0); 640 int end; 641 int steps = 0; 642 int last = selectedIndices.length(); 643 while (start >= 0) { 644 end = selectedIndices.nextClearBit(start); 645 steps++; 646 getSelectionModel().addSelectionInterval(start, end-1); 647 start = selectedIndices.nextSetBit(end); 648 if (start < 0 || end == last) 649 break; 650 } 651 return steps; 652 } 653 654 /** 655 * Replies true if the index-th relation members refers 656 * to an editable relation, i.e. a relation which is not 657 * incomplete. 658 * 659 * @param index the index 660 * @return true, if the index-th relation members refers 661 * to an editable relation, i.e. a relation which is not 662 * incomplete 663 */ 664 public boolean isEditableRelation(int index) { 665 if (index < 0 || index >= members.size()) 666 return false; 667 RelationMember member = members.get(index); 668 if (!member.isRelation()) 669 return false; 670 Relation r = member.getRelation(); 671 return !r.isIncomplete(); 672 } 673 674 /** 675 * Replies true if there is at least one relation member given as {@code members} 676 * which refers to at least on the primitives in {@code primitives}. 677 * 678 * @param members the members 679 * @param primitives the collection of primitives 680 * @return true if there is at least one relation member in this model 681 * which refers to at least on the primitives in <code>primitives</code>; false 682 * otherwise 683 */ 684 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) { 685 if (primitives == null || primitives.isEmpty()) 686 return false; 687 Set<OsmPrimitive> referrers = new HashSet<>(); 688 for (RelationMember member : members) { 689 referrers.add(member.getMember()); 690 } 691 for (OsmPrimitive referred : primitives) { 692 if (referrers.contains(referred)) 693 return true; 694 } 695 return false; 696 } 697 698 /** 699 * Replies true if there is at least one relation member in this model 700 * which refers to at least on the primitives in <code>primitives</code>. 701 * 702 * @param primitives the collection of primitives 703 * @return true if there is at least one relation member in this model 704 * which refers to at least on the primitives in <code>primitives</code>; false 705 * otherwise 706 */ 707 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) { 708 return hasMembersReferringTo(members, primitives); 709 } 710 711 /** 712 * Selects all members which refer to {@link OsmPrimitive}s in the collections 713 * <code>primitmives</code>. Does nothing is primitives is null. 714 * 715 * @param primitives the collection of primitives 716 */ 717 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) { 718 if (primitives == null) return; 719 getSelectionModel().setValueIsAdjusting(true); 720 getSelectionModel().clearSelection(); 721 BitSet selected = new BitSet(); 722 for (int i = 0; i < members.size(); i++) { 723 RelationMember m = members.get(i); 724 if (primitives.contains(m.getMember())) { 725 selected.set(i); 726 } 727 } 728 addToSelectedMembers(selected); 729 getSelectionModel().setValueIsAdjusting(false); 730 int[] selectedIndices = getSelectedIndices(); 731 if (selectedIndices.length > 0) { 732 fireMakeMemberVisible(selectedIndices[0]); 733 } 734 } 735 736 /** 737 * Replies true if <code>primitive</code> is currently selected in the layer this 738 * model is attached to 739 * 740 * @param primitive the primitive 741 * @return true if <code>primitive</code> is currently selected in the layer this 742 * model is attached to, false otherwise 743 */ 744 public boolean isInJosmSelection(OsmPrimitive primitive) { 745 return layer.data.isSelected(primitive); 746 } 747 748 /** 749 * Sort the selected relation members by the way they are linked. 750 */ 751 @Override 752 public void sort() { 753 List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers()); 754 List<RelationMember> sortedMembers; 755 List<RelationMember> newMembers; 756 if (selectedMembers.size() <= 1) { 757 newMembers = relationSorter.sortMembers(members); 758 sortedMembers = newMembers; 759 } else { 760 sortedMembers = relationSorter.sortMembers(selectedMembers); 761 List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices()); 762 newMembers = new ArrayList<>(); 763 boolean inserted = false; 764 for (int i = 0; i < members.size(); i++) { 765 if (selectedIndices.contains(i)) { 766 if (!inserted) { 767 newMembers.addAll(sortedMembers); 768 inserted = true; 769 } 770 } else { 771 newMembers.add(members.get(i)); 772 } 773 } 774 } 775 776 if (members.size() != newMembers.size()) 777 throw new AssertionError(); 778 779 members.clear(); 780 members.addAll(newMembers); 781 fireTableDataChanged(); 782 setSelectedMembers(sortedMembers); 783 } 784 785 /** 786 * Sort the selected relation members and all members below by the way they are linked. 787 */ 788 public void sortBelow() { 789 final List<RelationMember> subList = members.subList(Math.max(0, getSelectionModel().getMinSelectionIndex()), members.size()); 790 final List<RelationMember> sorted = relationSorter.sortMembers(subList); 791 subList.clear(); 792 subList.addAll(sorted); 793 fireTableDataChanged(); 794 setSelectedMembers(sorted); 795 } 796 797 WayConnectionType getWayConnection(int i) { 798 try { 799 if (connectionType == null) { 800 connectionType = wayConnectionTypeCalculator.updateLinks(members); 801 } 802 return connectionType.get(i); 803 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { 804 throw BugReport.intercept(e).put("i", i).put("members", members).put("relation", relation); 805 } 806 } 807 808 @Override 809 public void tableChanged(TableModelEvent e) { 810 invalidateConnectionType(); 811 } 812 813 private void invalidateConnectionType() { 814 connectionType = null; 815 } 816 817 /** 818 * Reverse the relation members. 819 */ 820 @Override 821 public void reverse() { 822 List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices()); 823 List<Integer> selectedIndicesReversed = ArrayUtils.toList(getSelectedIndices()); 824 825 if (selectedIndices.size() <= 1) { 826 Collections.reverse(members); 827 fireTableDataChanged(); 828 setSelectedMembers(members); 829 } else { 830 Collections.reverse(selectedIndicesReversed); 831 832 List<RelationMember> newMembers = new ArrayList<>(members); 833 834 for (int i = 0; i < selectedIndices.size(); i++) { 835 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i))); 836 } 837 838 if (members.size() != newMembers.size()) throw new AssertionError(); 839 members.clear(); 840 members.addAll(newMembers); 841 fireTableDataChanged(); 842 setSelectedMembersIdx(selectedIndices); 843 } 844 } 845}