source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java

Last change on this file was 19307, checked in by taylor.smock, 14 months ago

Fix most new PMD issues

It would be better to use the newer switch syntax introduced in Java 14 (JEP 361),
but we currently target Java 11+. When we move to Java 17, this should be
reverted and the newer switch syntax should be used.

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