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

Last change on this file since 17217 was 17217, checked in by GerdP, 4 years ago

fix #19913: IOOBE: Index 254 out of bounds for length 0: Saving a relation, after splitting a child member with open relation editor

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