diff --git a/resources/images/dialogs/relation/replaceselectedright.svg b/resources/images/dialogs/relation/replaceselectedright.svg
new file mode 100644
index 00000000000..9619f472d38
--- /dev/null
+++ b/resources/images/dialogs/relation/replaceselectedright.svg
@@ -0,0 +1,105 @@
+
+
+
+
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
index 743e05f4e08..9548916db73 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
@@ -86,6 +86,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;
@@ -703,10 +704,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 e30270d0d35..fc181457afe 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
@@ -505,6 +505,42 @@ public void updateRole(int[] idx, String role) {
addToSelectedMembers(selected);
}
+ /**
+ * updates the referenced primitive of the members given by the index in index
+ *
+ * @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 index 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..2ab2d342c74 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,59 @@ 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 filterConfirmedPrimitives(List 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} and the user decides to not add a primitive.
+ * @since xxx
+ */
+ protected List filterConfirmedPrimitives(List primitives, boolean abortOnSkip) throws AddAbortException {
if (Utils.isEmpty(primitives))
return primitives;
List ret = new ArrayList<>();
ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
- for (OsmPrimitive primitive : primitives) {
- if (primitive instanceof Relation) {
- List 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 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..b53d4d18328
--- /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 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 selection = getSelectionTableModel().getSelection();
+ int numSelectedPrimitives = selection.size();
+ if (numSelectedPrimitives != selectedMemberIndices.length) {
+ return;
+ }
+
+ List 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);
+ }
+ }
+}