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 // ignore 177 } 178 179 @Override 180 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 181 // ignore 182 } 183 184 /* --------------------------------------------------------------------------- */ 185 186 /** 187 * Add a new member model listener. 188 * @param listener member model listener to add 189 */ 190 public void addMemberModelListener(IMemberModelListener listener) { 191 if (listener != null) { 192 listeners.addIfAbsent(listener); 193 } 194 } 195 196 /** 197 * Remove a member model listener. 198 * @param listener member model listener to remove 199 */ 200 public void removeMemberModelListener(IMemberModelListener listener) { 201 listeners.remove(listener); 202 } 203 204 protected void fireMakeMemberVisible(int index) { 205 for (IMemberModelListener listener : listeners) { 206 listener.makeMemberVisible(index); 207 } 208 } 209 210 /** 211 * Populates this model from the given relation. 212 * @param relation relation 213 */ 214 public void populate(Relation relation) { 215 members.clear(); 216 if (relation != null) { 217 // make sure we work with clones of the relation members in the model. 218 members.addAll(new Relation(relation).getMembers()); 219 } 220 fireTableDataChanged(); 221 } 222 223 @Override 224 public int getColumnCount() { 225 return 3; 226 } 227 228 @Override 229 public int getRowCount() { 230 return members.size(); 231 } 232 233 @Override 234 public Object getValueAt(int rowIndex, int columnIndex) { 235 switch (columnIndex) { 236 case 0: 237 return members.get(rowIndex).getRole(); 238 case 1: 239 return members.get(rowIndex).getMember(); 240 case 2: 241 return getWayConnection(rowIndex); 242 } 243 // should not happen 244 return null; 245 } 246 247 @Override 248 public boolean isCellEditable(int rowIndex, int columnIndex) { 249 return columnIndex == 0; 250 } 251 252 @Override 253 public void setValueAt(Object value, int rowIndex, int columnIndex) { 254 // fix #10524 - IndexOutOfBoundsException: Index: 2, Size: 2 255 if (rowIndex >= members.size()) { 256 return; 257 } 258 RelationMember member = members.get(rowIndex); 259 String role = value.toString(); 260 if (member.hasRole(role)) 261 return; 262 RelationMember newMember = new RelationMember(role, member.getMember()); 263 members.remove(rowIndex); 264 members.add(rowIndex, newMember); 265 fireTableDataChanged(); 266 } 267 268 @Override 269 public OsmPrimitive getReferredPrimitive(int idx) { 270 return members.get(idx).getMember(); 271 } 272 273 @Override 274 public boolean move(int delta, int... selectedRows) { 275 if (!canMove(delta, this::getRowCount, selectedRows)) 276 return false; 277 doMove(delta, selectedRows); 278 fireTableDataChanged(); 279 final ListSelectionModel selectionModel = getSelectionModel(); 280 selectionModel.setValueIsAdjusting(true); 281 selectionModel.clearSelection(); 282 BitSet selected = new BitSet(); 283 for (int row : selectedRows) { 284 row += delta; 285 selected.set(row); 286 } 287 addToSelectedMembers(selected); 288 selectionModel.setValueIsAdjusting(false); 289 fireMakeMemberVisible(selectedRows[0] + delta); 290 return true; 291 } 292 293 /** 294 * Remove selected rows, if possible. 295 * @param selectedRows rows to remove 296 * @see #canRemove 297 */ 298 public void remove(int... selectedRows) { 299 if (!canRemove(selectedRows)) 300 return; 301 int offset = 0; 302 final ListSelectionModel selectionModel = getSelectionModel(); 303 selectionModel.setValueIsAdjusting(true); 304 for (int row : selectedRows) { 305 row -= offset; 306 if (members.size() > row) { 307 members.remove(row); 308 selectionModel.removeIndexInterval(row, row); 309 offset++; 310 } 311 } 312 selectionModel.setValueIsAdjusting(false); 313 fireTableDataChanged(); 314 } 315 316 /** 317 * Checks that a range of rows can be removed. 318 * @param rows indexes of rows to remove 319 * @return {@code true} if rows can be removed 320 */ 321 public boolean canRemove(int... rows) { 322 return rows != null && rows.length != 0; 323 } 324 325 @Override 326 public DefaultListSelectionModel getSelectionModel() { 327 if (listSelectionModel == null) { 328 listSelectionModel = new DefaultListSelectionModel(); 329 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 330 } 331 return listSelectionModel; 332 } 333 334 @Override 335 public RelationMember getValue(int index) { 336 return members.get(index); 337 } 338 339 @Override 340 public RelationMember setValue(int index, RelationMember value) { 341 return members.set(index, value); 342 } 343 344 /** 345 * Remove members referring to the given list of primitives. 346 * @param primitives list of OSM primitives 347 */ 348 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) { 349 if (primitives == null) 350 return; 351 remove(IntStream.range(0, members.size()).filter(i -> primitives.contains(members.get(i).getMember())).toArray()); 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.stream() 360 .filter(rm -> !rm.getMember().isDeleted()).collect(Collectors.toList())); 361 } 362 363 /** 364 * Determines if this model has the same members as the given relation. 365 * @param relation relation 366 * @return {@code true} if this model has the same members as {@code relation} 367 */ 368 public boolean hasSameMembersAs(Relation relation) { 369 if (relation == null || relation.getMembersCount() != members.size()) 370 return false; 371 for (int i = 0; i < relation.getMembersCount(); i++) { 372 if (!relation.getMember(i).equals(members.get(i))) 373 return false; 374 } 375 return true; 376 } 377 378 /** 379 * Replies the set of incomplete primitives 380 * 381 * @return the set of incomplete primitives 382 */ 383 public Set<OsmPrimitive> getIncompleteMemberPrimitives() { 384 return members.stream().map(RelationMember::getMember).filter(AbstractPrimitive::isIncomplete).collect(Collectors.toSet()); 385 } 386 387 /** 388 * Replies the set of selected incomplete primitives 389 * 390 * @return the set of selected incomplete primitives 391 */ 392 public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() { 393 return getSelectedMembers().stream().map(RelationMember::getMember).filter(AbstractPrimitive::isIncomplete).collect(Collectors.toSet()); 394 } 395 396 /** 397 * Replies true if at least one the relation members is incomplete 398 * 399 * @return true if at least one the relation members is incomplete 400 */ 401 public boolean hasIncompleteMembers() { 402 return members.stream().anyMatch(rm -> rm.getMember().isIncomplete()); 403 } 404 405 /** 406 * Replies true if at least one of the selected members is incomplete 407 * 408 * @return true if at least one of the selected members is incomplete 409 */ 410 public boolean hasIncompleteSelectedMembers() { 411 return getSelectedMembers().stream().anyMatch(rm -> rm.getMember().isIncomplete()); 412 } 413 414 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) { 415 if (primitives == null || primitives.isEmpty()) 416 return; 417 int idx = index; 418 for (OsmPrimitive primitive : primitives) { 419 final RelationMember member = getRelationMemberForPrimitive(primitive); 420 members.add(idx++, member); 421 } 422 fireTableDataChanged(); 423 getSelectionModel().clearSelection(); 424 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1); 425 fireMakeMemberVisible(index); 426 } 427 428 RelationMember getRelationMemberForPrimitive(final OsmPrimitive primitive) { 429 final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets( 430 EnumSet.of(relation != null ? TaggingPresetType.forPrimitive(relation) : TaggingPresetType.RELATION), 431 presetHandler.getSelection().iterator().next().getKeys(), false); 432 Collection<String> potentialRoles = new TreeSet<>(); 433 for (TaggingPreset tp : presets) { 434 String suggestedRole = tp.suggestRoleForOsmPrimitive(primitive); 435 if (suggestedRole != null) { 436 potentialRoles.add(suggestedRole); 437 } 438 } 439 // TODO: propose user to choose role among potential ones instead of picking first one 440 final String role = potentialRoles.isEmpty() ? "" : potentialRoles.iterator().next(); 441 return new RelationMember(role == null ? "" : role, primitive); 442 } 443 444 void addMembersAtIndexKeepingOldSelection(final Iterable<RelationMember> newMembers, final int index) { 445 int idx = index; 446 for (RelationMember member : newMembers) { 447 members.add(idx++, member); 448 } 449 invalidateConnectionType(); 450 fireTableRowsInserted(index, idx - 1); 451 } 452 453 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) { 454 addMembersAtIndex(primitives, 0); 455 } 456 457 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) { 458 addMembersAtIndex(primitives, members.size()); 459 } 460 461 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) { 462 addMembersAtIndex(primitives, idx); 463 } 464 465 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) { 466 addMembersAtIndex(primitives, idx + 1); 467 } 468 469 /** 470 * Replies the number of members which refer to a particular primitive 471 * 472 * @param primitive the primitive 473 * @return the number of members which refer to a particular primitive 474 */ 475 public int getNumMembersWithPrimitive(OsmPrimitive primitive) { 476 int count = 0; 477 for (RelationMember member : members) { 478 if (member.getMember().equals(primitive)) { 479 count++; 480 } 481 } 482 return count; 483 } 484 485 /** 486 * updates the role of the members given by the indices in <code>idx</code> 487 * 488 * @param idx the array of indices 489 * @param role the new role 490 */ 491 public void updateRole(int[] idx, String role) { 492 if (idx == null || idx.length == 0) 493 return; 494 for (int row : idx) { 495 // fix #7885 - IndexOutOfBoundsException: Index: 39, Size: 39 496 if (row >= members.size()) { 497 continue; 498 } 499 RelationMember oldMember = members.get(row); 500 RelationMember newMember = new RelationMember(role, oldMember.getMember()); 501 members.remove(row); 502 members.add(row, newMember); 503 } 504 fireTableDataChanged(); 505 BitSet selected = new BitSet(); 506 for (int row : idx) { 507 selected.set(row); 508 } 509 addToSelectedMembers(selected); 510 } 511 512 /** 513 * Get the currently selected relation members 514 * 515 * @return a collection with the currently selected relation members 516 */ 517 public Collection<RelationMember> getSelectedMembers() { 518 List<RelationMember> selectedMembers = new ArrayList<>(); 519 for (int i : getSelectedIndices()) { 520 selectedMembers.add(members.get(i)); 521 } 522 return selectedMembers; 523 } 524 525 /** 526 * Replies the set of selected referers. Never null, but may be empty. 527 * 528 * @return the set of selected referers 529 */ 530 public Collection<OsmPrimitive> getSelectedChildPrimitives() { 531 Collection<OsmPrimitive> ret = new ArrayList<>(); 532 for (RelationMember m: getSelectedMembers()) { 533 ret.add(m.getMember()); 534 } 535 return ret; 536 } 537 538 /** 539 * Replies the set of selected referers. Never null, but may be empty. 540 * @param referenceSet reference set 541 * 542 * @return the set of selected referers 543 */ 544 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) { 545 Set<OsmPrimitive> ret = new HashSet<>(); 546 if (referenceSet == null) return null; 547 for (RelationMember m: members) { 548 if (referenceSet.contains(m.getMember())) { 549 ret.add(m.getMember()); 550 } 551 } 552 return ret; 553 } 554 555 /** 556 * Selects the members in the collection selectedMembers 557 * 558 * @param selectedMembers the collection of selected members 559 */ 560 public void setSelectedMembers(Collection<RelationMember> selectedMembers) { 561 if (selectedMembers == null || selectedMembers.isEmpty()) { 562 getSelectionModel().clearSelection(); 563 return; 564 } 565 566 // lookup the indices for the respective members 567 // 568 Set<Integer> selectedIndices = new HashSet<>(); 569 for (RelationMember member : selectedMembers) { 570 for (int idx = 0; idx < members.size(); ++idx) { 571 if (member.equals(members.get(idx))) { 572 selectedIndices.add(idx); 573 } 574 } 575 } 576 setSelectedMembersIdx(selectedIndices); 577 } 578 579 /** 580 * Selects the members in the collection selectedIndices 581 * 582 * @param selectedIndices the collection of selected member indices 583 */ 584 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) { 585 if (selectedIndices == null || selectedIndices.isEmpty()) { 586 getSelectionModel().clearSelection(); 587 return; 588 } 589 // select the members 590 // 591 getSelectionModel().setValueIsAdjusting(true); 592 getSelectionModel().clearSelection(); 593 BitSet selected = new BitSet(); 594 for (int row : selectedIndices) { 595 selected.set(row); 596 } 597 addToSelectedMembers(selected); 598 getSelectionModel().setValueIsAdjusting(false); 599 // make the first selected member visible 600 // 601 if (!selectedIndices.isEmpty()) { 602 fireMakeMemberVisible(Collections.min(selectedIndices)); 603 } 604 } 605 606 /** 607 * Add one or more members indices to the selection. 608 * Detect groups of consecutive indices. 609 * Only one costly call of addSelectionInterval is performed for each group 610 611 * @param selectedIndices selected indices as a bitset 612 * @return number of groups 613 */ 614 private int addToSelectedMembers(BitSet selectedIndices) { 615 if (selectedIndices == null || selectedIndices.isEmpty()) { 616 return 0; 617 } 618 // select the members 619 // 620 int start = selectedIndices.nextSetBit(0); 621 int end; 622 int steps = 0; 623 int last = selectedIndices.length(); 624 while (start >= 0) { 625 end = selectedIndices.nextClearBit(start); 626 steps++; 627 getSelectionModel().addSelectionInterval(start, end-1); 628 start = selectedIndices.nextSetBit(end); 629 if (start < 0 || end == last) 630 break; 631 } 632 return steps; 633 } 634 635 /** 636 * Replies true if the index-th relation members refers 637 * to an editable relation, i.e. a relation which is not 638 * incomplete. 639 * 640 * @param index the index 641 * @return true, if the index-th relation members refers 642 * to an editable relation, i.e. a relation which is not 643 * incomplete 644 */ 645 public boolean isEditableRelation(int index) { 646 if (index < 0 || index >= members.size()) 647 return false; 648 RelationMember member = members.get(index); 649 if (!member.isRelation()) 650 return false; 651 Relation r = member.getRelation(); 652 return !r.isIncomplete(); 653 } 654 655 /** 656 * Replies true if there is at least one relation member given as {@code members} 657 * which refers to at least on the primitives in {@code primitives}. 658 * 659 * @param members the members 660 * @param primitives the collection of primitives 661 * @return true if there is at least one relation member in this model 662 * which refers to at least on the primitives in <code>primitives</code>; false 663 * otherwise 664 */ 665 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) { 666 if (primitives == null || primitives.isEmpty()) 667 return false; 668 Set<OsmPrimitive> referrers = new HashSet<>(); 669 for (RelationMember member : members) { 670 referrers.add(member.getMember()); 671 } 672 for (OsmPrimitive referred : primitives) { 673 if (referrers.contains(referred)) 674 return true; 675 } 676 return false; 677 } 678 679 /** 680 * Replies 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>. 682 * 683 * @param primitives the collection of primitives 684 * @return true if there is at least one relation member in this model 685 * which refers to at least on the primitives in <code>primitives</code>; false 686 * otherwise 687 */ 688 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) { 689 return hasMembersReferringTo(members, primitives); 690 } 691 692 /** 693 * Selects all members which refer to {@link OsmPrimitive}s in the collections 694 * <code>primitmives</code>. Does nothing is primitives is null. 695 * 696 * @param primitives the collection of primitives 697 */ 698 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) { 699 if (primitives == null) return; 700 getSelectionModel().setValueIsAdjusting(true); 701 getSelectionModel().clearSelection(); 702 BitSet selected = new BitSet(); 703 for (int i = 0; i < members.size(); i++) { 704 RelationMember m = members.get(i); 705 if (primitives.contains(m.getMember())) { 706 selected.set(i); 707 } 708 } 709 addToSelectedMembers(selected); 710 getSelectionModel().setValueIsAdjusting(false); 711 int[] selectedIndices = getSelectedIndices(); 712 if (selectedIndices.length > 0) { 713 fireMakeMemberVisible(selectedIndices[0]); 714 } 715 } 716 717 /** 718 * Replies true if <code>primitive</code> is currently selected in the layer this 719 * model is attached to 720 * 721 * @param primitive the primitive 722 * @return true if <code>primitive</code> is currently selected in the layer this 723 * model is attached to, false otherwise 724 */ 725 public boolean isInJosmSelection(OsmPrimitive primitive) { 726 return layer.data.isSelected(primitive); 727 } 728 729 /** 730 * Sort the selected relation members by the way they are linked. 731 */ 732 @Override 733 public void sort() { 734 List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers()); 735 List<RelationMember> sortedMembers; 736 List<RelationMember> newMembers; 737 if (selectedMembers.size() <= 1) { 738 newMembers = relationSorter.sortMembers(members); 739 sortedMembers = newMembers; 740 } else { 741 sortedMembers = relationSorter.sortMembers(selectedMembers); 742 List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices()); 743 newMembers = new ArrayList<>(); 744 boolean inserted = false; 745 for (int i = 0; i < members.size(); i++) { 746 if (selectedIndices.contains(i)) { 747 if (!inserted) { 748 newMembers.addAll(sortedMembers); 749 inserted = true; 750 } 751 } else { 752 newMembers.add(members.get(i)); 753 } 754 } 755 } 756 757 if (members.size() != newMembers.size()) 758 throw new AssertionError(); 759 760 members.clear(); 761 members.addAll(newMembers); 762 fireTableDataChanged(); 763 setSelectedMembers(sortedMembers); 764 } 765 766 /** 767 * Sort the selected relation members and all members below by the way they are linked. 768 */ 769 public void sortBelow() { 770 final List<RelationMember> subList = members.subList(Math.max(0, getSelectionModel().getMinSelectionIndex()), members.size()); 771 final List<RelationMember> sorted = relationSorter.sortMembers(subList); 772 subList.clear(); 773 subList.addAll(sorted); 774 fireTableDataChanged(); 775 setSelectedMembers(sorted); 776 } 777 778 WayConnectionType getWayConnection(int i) { 779 try { 780 if (connectionType == null) { 781 connectionType = wayConnectionTypeCalculator.updateLinks(members); 782 } 783 return connectionType.get(i); 784 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { 785 throw BugReport.intercept(e).put("i", i).put("members", members).put("relation", relation); 786 } 787 } 788 789 @Override 790 public void tableChanged(TableModelEvent e) { 791 invalidateConnectionType(); 792 } 793 794 private void invalidateConnectionType() { 795 connectionType = null; 796 } 797 798 /** 799 * Reverse the relation members. 800 */ 801 @Override 802 public void reverse() { 803 List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices()); 804 List<Integer> selectedIndicesReversed = ArrayUtils.toList(getSelectedIndices()); 805 806 if (selectedIndices.size() <= 1) { 807 Collections.reverse(members); 808 fireTableDataChanged(); 809 setSelectedMembers(members); 810 } else { 811 Collections.reverse(selectedIndicesReversed); 812 813 List<RelationMember> newMembers = new ArrayList<>(members); 814 815 for (int i = 0; i < selectedIndices.size(); i++) { 816 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i))); 817 } 818 819 if (members.size() != newMembers.size()) throw new AssertionError(); 820 members.clear(); 821 members.addAll(newMembers); 822 fireTableDataChanged(); 823 setSelectedMembersIdx(selectedIndices); 824 } 825 } 826}