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

Last change on this file since 16438 was 16438, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

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