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