diff --git a/resources/images/dialogs/relation/replaceselectedright.svg b/resources/images/dialogs/relation/replaceselectedright.svg new file mode 100644 index 00000000000..e5e6b5e3e29 --- /dev/null +++ b/resources/images/dialogs/relation/replaceselectedright.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24" height="24" version="1.0" xmlns="http://www.w3.org/2000/svg"> + <rect x="1.957" y="2.457" width="11.336" height="19.273" fill="#fff" stroke="#646464" stroke-width="1.0116"/> + <path d="m1.75 4.375 6.75-0.0625" fill="none"/> + <g stroke-linecap="round"> + <rect x="2.5767" y="9.8496" width="3.4312" height="3.0508" fill="#8faaff" stroke="#8faaff" stroke-width=".24516"/> + <rect x="6.2972" y="9.8321" width="6.393" height="3.0858" fill="#faa" stroke="#faa" stroke-width=".33697"/> + <path d="m22.163 19.788v-14.076l-5.9509 7.0229 5.9509 7.053z" fill="#c8c8c8" fill-rule="evenodd" stroke="#c8c8c8" stroke-linejoin="round" stroke-width="1.9865"/> + </g> + <g> + <path d="m21.819 18.694v-14.076l-5.9509 7.0229 5.9509 7.053z" fill="#000080" fill-rule="evenodd" stroke="#000080" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9865"/> + <path d="m2.3225 9.5613 10.605-0.00845" fill="none" stroke="#646464" stroke-width=".62002"/> + <path d="m2.3225 13.197 10.605-0.0084" fill="none" stroke="#646464" stroke-width=".62002"/> + </g> +</svg> diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java index f597a078c0d..74aca9559c6 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java @@ -88,6 +88,7 @@ import org.openstreetmap.josm.gui.dialogs.relation.actions.RefreshAction; import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction; import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction; +import org.openstreetmap.josm.gui.dialogs.relation.actions.ReplaceSelectedAction; import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction; import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectAction; import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction; @@ -737,10 +738,13 @@ protected static JToolBar buildSelectionControlButtonToolbar(IRelationEditorActi new AddSelectedAtEndAction(editorAccess) )); groups.add(buildNativeGroup(20, + new ReplaceSelectedAction(editorAccess) + )); + groups.add(buildNativeGroup(30, new SelectedMembersForSelectionAction(editorAccess), new SelectPrimitivesForSelectedMembersAction(editorAccess) )); - groups.add(buildNativeGroup(30, + groups.add(buildNativeGroup(40, new RemoveSelectedAction(editorAccess) )); groups.addAll(RelationEditorHooks.getSelectActions()); diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java index 962ccc25aff..1c5bcfe8c83 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java @@ -520,6 +520,42 @@ public void updateRole(int[] idx, String role) { addToSelectedMembers(selected); } + /** + * updates the referenced primitive of the members given by the index in <code>index</code> + * + * @param index the index to update + * @param newPrimitive the new primitive + * @since xxx + */ + public void updateMemberPrimitive(int index, OsmPrimitive newPrimitive) { + if (index >= members.size()) { + return; + } + + RelationMember newMember = new RelationMember(members.get(index).getRole(), newPrimitive); + updateMember(index, newMember); + } + + /** + * replace the member at <code>index</code> with a new one + * + * @param index the index to update + * @param newMember the new member + * @since xxx + */ + public void updateMember(int index, RelationMember newMember) { + if (index >= members.size()) { + return; + } + + RelationMember oldMember = members.get(index); + if (oldMember.equals(newMember)) + return; + + setValue(index, newMember); + fireTableDataChanged(); + } + /** * Get the currently selected relation members * diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java index b1740cc06ac..2d729b840c0 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java @@ -29,28 +29,61 @@ protected boolean isPotentialDuplicate(OsmPrimitive primitive) { return editorAccess.getMemberTableModel().hasMembersReferringTo(Collections.singleton(primitive)); } + /** + * Check and filter a list of primitives before adding them as relation members. + * Prompt users for confirmation when duplicates are detected and prevent relation loops. + * + * @param primitives The primitives to be checked and filtered + * @return The primitives to add to the relation. Never {@code null}, but may be an empty list. + * @throws AddAbortException when a relation loop is detected + */ protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException { + return filterConfirmedPrimitives(primitives, false); + } + + /** + * Check and filter a list of primitives before adding them as relation members. + * Prompt users for confirmation when duplicates are detected and prevent relation loops. + * + * @param primitives The primitives to be checked and filtered + * @param abortOnSkip If the user decides to not add a primitive or adding a primitive would + * cause a relation loop, abort (throw {@code AddAbortException}) + * @return The primitives to add to the relation. Never {@code null}, but may be an empty list. + * @throws AddAbortException when a relation loop is detected or {@code abortOnSkip} is + * {@code true} <i>and</i> the user decides to not add a primitive. + * @since xxx + */ + protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives, boolean abortOnSkip) throws AddAbortException { if (Utils.isEmpty(primitives)) return primitives; List<OsmPrimitive> ret = new ArrayList<>(); ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation"); - for (OsmPrimitive primitive : primitives) { - if (primitive instanceof Relation) { - List<Relation> loop = RelationChecker.checkAddMember(editorAccess.getEditor().getRelation(), (Relation) primitive); - if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) { - GenericRelationEditor.warnOfCircularReferences(primitive, loop); - continue; + try { + for (OsmPrimitive primitive : primitives) { + if (primitive instanceof Relation) { + List<Relation> loop = RelationChecker.checkAddMember(editorAccess.getEditor().getRelation(), (Relation) primitive); + if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) { + GenericRelationEditor.warnOfCircularReferences(primitive, loop); + if (abortOnSkip) { + throw new AddAbortException(); + } + continue; + } } - } - if (isPotentialDuplicate(primitive)) { - if (GenericRelationEditor.confirmAddingPrimitive(primitive)) { + if (isPotentialDuplicate(primitive)) { + if (GenericRelationEditor.confirmAddingPrimitive(primitive)) { + ret.add(primitive); + } else if (abortOnSkip) { + throw new AddAbortException(); + } + } else { ret.add(primitive); } - } else { - ret.add(primitive); } + } finally { + ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation"); } - ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation"); + return ret; } } diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java new file mode 100644 index 00000000000..097225878d3 --- /dev/null +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java @@ -0,0 +1,57 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.dialogs.relation.actions; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.util.List; + +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException; +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Logging; + +/** + * Replace selected relation members with the objects selected in the current dataset + * @since xxx + */ +public class ReplaceSelectedAction extends AddFromSelectionAction { + + /** + * Constructs a new {@code ReplaceSelectedAction}. + * @param editorAccess An interface to access the relation editor contents. + */ + public ReplaceSelectedAction(IRelationEditorActionAccess editorAccess) { + super(editorAccess, IRelationEditorUpdateOn.MEMBER_TABLE_SELECTION, IRelationEditorUpdateOn.SELECTION_TABLE_CHANGE); + putValue(SHORT_DESCRIPTION, tr("Replace selected members with selected objects")); + new ImageProvider("dialogs/relation", "replaceselectedright").getResource().attachImageIcon(this, true); + updateEnabledState(); + } + + @Override + protected void updateEnabledState() { + int numSelected = getSelectionTableModel().getRowCount(); + setEnabled(numSelected > 0 && + numSelected == getMemberTableModel().getSelectedIndices().length); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + int[] selectedMemberIndices = getMemberTableModel().getSelectedIndices(); + List<OsmPrimitive> selection = getSelectionTableModel().getSelection(); + int numSelectedPrimitives = selection.size(); + if (numSelectedPrimitives != selectedMemberIndices.length) { + return; + } + + List<OsmPrimitive> filteredSelection = filterConfirmedPrimitives(selection, true); + + for (int i = 0; i < selectedMemberIndices.length; i++) { + getMemberTableModel().updateMemberPrimitive(selectedMemberIndices[i], filteredSelection.get(i)); + } + } catch (AddAbortException ex) { + Logging.trace(ex); + } + } +}