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