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