| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.gui.dialogs.relation;
|
|---|
| 3 |
|
|---|
| 4 | import java.util.ArrayList;
|
|---|
| 5 | import java.util.BitSet;
|
|---|
| 6 | import java.util.Collection;
|
|---|
| 7 | import java.util.Collections;
|
|---|
| 8 | import java.util.EnumSet;
|
|---|
| 9 | import java.util.HashSet;
|
|---|
| 10 | import java.util.List;
|
|---|
| 11 | import java.util.Objects;
|
|---|
| 12 | import java.util.Set;
|
|---|
| 13 | import java.util.TreeSet;
|
|---|
| 14 | import java.util.concurrent.CopyOnWriteArrayList;
|
|---|
| 15 | import java.util.stream.Collectors;
|
|---|
| 16 | import java.util.stream.IntStream;
|
|---|
| 17 |
|
|---|
| 18 | import javax.swing.DefaultListSelectionModel;
|
|---|
| 19 | import javax.swing.ListSelectionModel;
|
|---|
| 20 | import javax.swing.SwingUtilities;
|
|---|
| 21 | import javax.swing.event.TableModelEvent;
|
|---|
| 22 | import javax.swing.event.TableModelListener;
|
|---|
| 23 | import javax.swing.table.AbstractTableModel;
|
|---|
| 24 |
|
|---|
| 25 | import org.openstreetmap.josm.data.osm.AbstractPrimitive;
|
|---|
| 26 | import org.openstreetmap.josm.data.osm.DataSelectionListener;
|
|---|
| 27 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
|---|
| 28 | import org.openstreetmap.josm.data.osm.Relation;
|
|---|
| 29 | import org.openstreetmap.josm.data.osm.RelationMember;
|
|---|
| 30 | import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
|
|---|
| 31 | import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
|
|---|
| 32 | import org.openstreetmap.josm.data.osm.event.DataSetListener;
|
|---|
| 33 | import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
|
|---|
| 34 | import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
|
|---|
| 35 | import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
|
|---|
| 36 | import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
|
|---|
| 37 | import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
|
|---|
| 38 | import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
|
|---|
| 39 | import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
|
|---|
| 40 | import org.openstreetmap.josm.gui.MainApplication;
|
|---|
| 41 | import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;
|
|---|
| 42 | import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
|
|---|
| 43 | import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
|
|---|
| 44 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
|---|
| 45 | import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
|
|---|
| 46 | import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
|
|---|
| 47 | import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
|
|---|
| 48 | import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
|
|---|
| 49 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
|---|
| 50 | import org.openstreetmap.josm.gui.util.SortableTableModel;
|
|---|
| 51 | import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
|
|---|
| 52 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
|---|
| 53 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 54 | import 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 | */
|
|---|
| 59 | public class MemberTableModel extends AbstractTableModel
|
|---|
| 60 | implements 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 | }
|
|---|