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