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