Index: trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 9493)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 9496)
@@ -331,5 +331,5 @@
      *
      * @param layer the layer in whose context the relations are deleted. Must not be null.
-     * @param toDelete  the relations to be deleted. Must not be null.
+     * @param toDelete the relations to be deleted. Must not be null.
      * @throws IllegalArgumentException if layer is null
      * @throws IllegalArgumentException if toDelete is null
@@ -344,6 +344,6 @@
             Main.main.undoRedo.add(cmd);
             for (Relation relation : toDelete) {
-                if (getCurrentDataSet().getSelectedRelations().contains(relation)) {
-                    getCurrentDataSet().toggleSelected(relation);
+                if (layer.data.getSelectedRelations().contains(relation)) {
+                    layer.data.toggleSelected(relation);
                 }
                 RelationDialogManager.getRelationDialogManager().close(layer, relation);
Index: trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java	(revision 9493)
+++ trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java	(revision 9496)
@@ -5,6 +5,6 @@
 
 import java.awt.Component;
+import java.awt.GraphicsEnvironment;
 import java.awt.GridBagLayout;
-import java.awt.HeadlessException;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -113,14 +113,20 @@
      * @param defaultOption the default option; only meaningful if options is used; can be null
      *
-     * @return the option selected by user. {@link JOptionPane#CLOSED_OPTION} if the dialog was closed.
-     * @throws HeadlessException if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
+     * @return the option selected by user.
+     *         {@link JOptionPane#CLOSED_OPTION} if the dialog was closed.
+     *         {@link JOptionPane#YES_OPTION} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
      */
     public static int showOptionDialog(String preferenceKey, Component parent, Object message, String title, int optionType,
-            int messageType, Object[] options, Object defaultOption) throws HeadlessException {
+            int messageType, Object[] options, Object defaultOption) {
         int ret = getDialogReturnValue(preferenceKey);
         if (isYesOrNo(ret))
             return ret;
         MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
-        ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption);
+        if (GraphicsEnvironment.isHeadless()) {
+            // for unit tests
+            ret = JOptionPane.YES_OPTION;
+        } else {
+            ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption);
+        }
         if (isYesOrNo(ret)) {
             pnl.getNotShowAgain().store(preferenceKey, ret);
@@ -151,9 +157,9 @@
      * @param optionType  the option type
      * @param messageType the message type
-     * @param trueOption  if this option is selected the method replies true
+     * @param trueOption if this option is selected the method replies true
      *
      *
      * @return true, if the selected option is equal to <code>trueOption</code>, otherwise false.
-     * @throws HeadlessException if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
+     *         {@code trueOption} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
      *
      * @see JOptionPane#INFORMATION_MESSAGE
@@ -162,10 +168,15 @@
      */
     public static boolean showConfirmationDialog(String preferenceKey, Component parent, Object message, String title,
-            int optionType, int messageType, int trueOption) throws HeadlessException {
+            int optionType, int messageType, int trueOption) {
         int ret = getDialogReturnValue(preferenceKey);
         if (isYesOrNo(ret))
             return ret == trueOption;
         MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
-        ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType);
+        if (GraphicsEnvironment.isHeadless()) {
+            // for unit tests
+            ret = trueOption;
+        } else {
+            ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType);
+        }
         if (isYesOrNo(ret)) {
             pnl.getNotShowAgain().store(preferenceKey, ret);
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 9493)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 9496)
@@ -4,5 +4,4 @@
 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.BorderLayout;
@@ -21,6 +20,4 @@
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -46,26 +43,15 @@
 import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
-import javax.swing.event.TableModelEvent;
-import javax.swing.event.TableModelListener;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.CopyAction;
 import org.openstreetmap.josm.actions.ExpertToggleAction;
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.command.AddCommand;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.conflict.ConflictAddCommand;
-import org.openstreetmap.josm.data.conflict.Conflict;
-import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
@@ -73,12 +59,33 @@
 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedBeforeSelection;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.ApplyAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.CancelAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.CopyMembersAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.DeleteCurrentRelationAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadIncompleteMembersAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadSelectedIncompleteMembersAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.EditAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveDownAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveUpAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.OKAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.PasteMembersAction;
+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.ReverseAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectedMembersForSelectionAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.SetRoleAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.SortAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction;
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.TagEditorModel;
 import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
@@ -88,7 +95,5 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
-import org.openstreetmap.josm.io.OnlineResource;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
-import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.WindowGeometry;
@@ -119,5 +124,5 @@
     private JMenuItem windowMenuItem;
     /**
-     * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.SortBelowAction}.
+     * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction}.
      */
     private JButton sortBelowButton;
@@ -134,5 +139,5 @@
      */
     public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
-        super(layer, relation, selectedMembers);
+        super(layer, relation);
 
         setRememberWindowGeometry(getClass().getName() + ".geometry",
@@ -227,6 +232,15 @@
                 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke());
                 // CHECKSTYLE.ON: LineLength
-        registerCopyPasteAction(new PasteMembersAction(), "PASTE_MEMBERS", Shortcut.getPasteKeyStroke());
-        registerCopyPasteAction(new CopyMembersAction(), "COPY_MEMBERS", Shortcut.getCopyKeyStroke());
+
+        registerCopyPasteAction(new PasteMembersAction(memberTableModel, getLayer(), this) {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                super.actionPerformed(e);
+                tfRole.requestFocusInWindow();
+            }
+        }, "PASTE_MEMBERS", Shortcut.getPasteKeyStroke());
+
+        registerCopyPasteAction(new CopyMembersAction(memberTableModel, getLayer(), this),
+                "COPY_MEMBERS", Shortcut.getCopyKeyStroke());
 
         tagEditorPanel.setNextFocusComponent(memberTable);
@@ -244,7 +258,7 @@
         JToolBar tb  = new JToolBar();
         tb.setFloatable(false);
-        tb.add(new ApplyAction());
-        tb.add(new DuplicateRelationAction());
-        DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction();
+        tb.add(new ApplyAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this));
+        tb.add(new DuplicateRelationAction(memberTableModel, tagEditorPanel.getModel(), getLayer()));
+        DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction(getLayer(), this);
         addPropertyChangeListener(deleteAction);
         tb.add(deleteAction);
@@ -258,9 +272,7 @@
      */
     protected JPanel buildOkCancelButtonPanel() {
-        JPanel pnl = new JPanel();
-        pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
-
-        pnl.add(new SideButton(new OKAction()));
-        pnl.add(new SideButton(new CancelAction()));
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
+        pnl.add(new SideButton(new OKAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole)));
+        pnl.add(new SideButton(new CancelAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole)));
         pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
         return pnl;
@@ -273,6 +285,5 @@
      */
     protected JPanel buildTagEditorPanel() {
-        JPanel pnl = new JPanel();
-        pnl.setLayout(new GridBagLayout());
+        JPanel pnl = new JPanel(new GridBagLayout());
 
         GridBagConstraints gc = new GridBagConstraints();
@@ -366,5 +377,5 @@
         tfRole.setText(Main.pref.get("relation.editor.generic.lastrole", ""));
         p3.add(tfRole);
-        SetRoleAction setRoleAction = new SetRoleAction();
+        SetRoleAction setRoleAction = new SetRoleAction(memberTable, memberTableModel, tfRole);
         memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
         tfRole.getDocument().addDocumentListener(setRoleAction);
@@ -488,34 +499,31 @@
 
         // -- move up action
-        MoveUpAction moveUpAction = new MoveUpAction();
+        MoveUpAction moveUpAction = new MoveUpAction(memberTable, memberTableModel, "moveUp");
         memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction);
         tb.add(moveUpAction);
-        memberTable.getActionMap().put("moveUp", moveUpAction);
 
         // -- move down action
-        MoveDownAction moveDownAction = new MoveDownAction();
+        MoveDownAction moveDownAction = new MoveDownAction(memberTable, memberTableModel, "moveDown");
         memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction);
         tb.add(moveDownAction);
-        memberTable.getActionMap().put("moveDown", moveDownAction);
 
         tb.addSeparator();
 
         // -- edit action
-        EditAction editAction = new EditAction();
+        EditAction editAction = new EditAction(memberTable, memberTableModel, getLayer());
         memberTableModel.getSelectionModel().addListSelectionListener(editAction);
         tb.add(editAction);
 
         // -- delete action
-        RemoveAction removeSelectedAction = new RemoveAction();
+        RemoveAction removeSelectedAction = new RemoveAction(memberTable, memberTableModel, "removeSelected");
         memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction);
         tb.add(removeSelectedAction);
-        memberTable.getActionMap().put("removeSelected", removeSelectedAction);
 
         tb.addSeparator();
         // -- sort action
-        SortAction sortAction = new SortAction();
+        SortAction sortAction = new SortAction(memberTable, memberTableModel);
         memberTableModel.addTableModelListener(sortAction);
         tb.add(sortAction);
-        final SortBelowAction sortBelowAction = new SortBelowAction();
+        final SortBelowAction sortBelowAction = new SortBelowAction(memberTable, memberTableModel);
         memberTableModel.addTableModelListener(sortBelowAction);
         memberTableModel.getSelectionModel().addListSelectionListener(sortBelowAction);
@@ -523,5 +531,5 @@
 
         // -- reverse action
-        ReverseAction reverseAction = new ReverseAction();
+        ReverseAction reverseAction = new ReverseAction(memberTable, memberTableModel);
         memberTableModel.addTableModelListener(reverseAction);
         tb.add(reverseAction);
@@ -530,11 +538,12 @@
 
         // -- download action
-        DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction();
+        DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction(
+                memberTable, memberTableModel, "downloadIncomplete", getLayer(), this);
         memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction);
         tb.add(downloadIncompleteMembersAction);
-        memberTable.getActionMap().put("downloadIncomplete", downloadIncompleteMembersAction);
 
         // -- download selected action
-        DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction();
+        DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction(
+                memberTable, memberTableModel, null, getLayer(), this);
         memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction);
         memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction);
@@ -560,10 +569,12 @@
 
         // -- add at start action
-        AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction();
+        AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction(
+                memberTableModel, selectionTableModel, this);
         selectionTableModel.addTableModelListener(addSelectionAction);
         tb.add(addSelectionAction);
 
         // -- add before selected action
-        AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection();
+        AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection(
+                memberTableModel, selectionTableModel, this);
         selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction);
         memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction);
@@ -571,5 +582,6 @@
 
         // -- add after selected action
-        AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection();
+        AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection(
+                memberTableModel, selectionTableModel, this);
         selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction);
         memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction);
@@ -577,5 +589,6 @@
 
         // -- add at end action
-        AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction();
+        AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction(
+                memberTableModel, selectionTableModel, this);
         selectionTableModel.addTableModelListener(addSelectedAtEndAction);
         tb.add(addSelectedAtEndAction);
@@ -584,5 +597,6 @@
 
         // -- select members action
-        SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction();
+        SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction(
+                memberTableModel, selectionTableModel, getLayer());
         selectionTableModel.addTableModelListener(selectMembersForSelectionAction);
         memberTableModel.addTableModelListener(selectMembersForSelectionAction);
@@ -590,5 +604,6 @@
 
         // -- select action
-        SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction();
+        SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction(
+                memberTable, memberTableModel, getLayer());
         memberTable.getSelectionModel().addListSelectionListener(selectAction);
         tb.add(selectAction);
@@ -597,5 +612,5 @@
 
         // -- remove selected action
-        RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction();
+        RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction(memberTableModel, selectionTableModel, getLayer());
         selectionTableModel.addTableModelListener(removeSelectedAction);
         tb.add(removeSelectedAction);
@@ -708,8 +723,17 @@
     }
 
-    static class AddAbortException extends Exception {
-    }
-
-    static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
+    /**
+     * Exception thrown when user aborts add operation.
+     */
+    public static class AddAbortException extends Exception {
+    }
+
+    /**
+     * Asks confirmationbefore adding a primitive.
+     * @param primitive primitive to add
+     * @return {@code true} is user confirms the operation, {@code false} otherwise
+     * @throws AddAbortException if user aborts operation
+     */
+    public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
         String msg = tr("<html>This relation already has one or more members referring to<br>"
                 + "the object ''{0}''<br>"
@@ -736,11 +760,14 @@
             return false;
         case JOptionPane.CANCEL_OPTION:
+        default:
             throw new AddAbortException();
         }
-        // should not happen
-        return false;
-    }
-
-    static void warnOfCircularReferences(OsmPrimitive primitive) {
+    }
+
+    /**
+     * Warn about circular references.
+     * @param primitive the concerned primitive
+     */
+    public static void warnOfCircularReferences(OsmPrimitive primitive) {
         String msg = tr("<html>You are trying to add a relation to itself.<br>"
                 + "<br>"
@@ -800,1027 +827,9 @@
     }
 
-    abstract class AddFromSelectionAction extends AbstractAction {
-        protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
-            return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
-        }
-
-        protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
-            if (primitives == null || primitives.isEmpty())
-                return primitives;
-            List<OsmPrimitive> ret = new ArrayList<>();
-            ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
-            for (OsmPrimitive primitive : primitives) {
-                if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) {
-                    warnOfCircularReferences(primitive);
-                    continue;
-                }
-                if (isPotentialDuplicate(primitive)) {
-                    if (confirmAddingPrimitive(primitive)) {
-                        ret.add(primitive);
-                    }
-                    continue;
-                } else {
-                    ret.add(primitive);
-                }
-            }
-            ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
-            return ret;
-        }
-    }
-
-    class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener {
-        AddSelectedAtStartAction() {
-            putValue(SHORT_DESCRIPTION,
-                    tr("Add all objects selected in the current dataset before the first member"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(selectionTableModel.getRowCount() > 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
-                memberTableModel.addMembersAtBeginning(toAdd);
-            } catch (AddAbortException ex) {
-                // do nothing
-                if (Main.isTraceEnabled()) {
-                    Main.trace(ex.getMessage());
-                }
-            }
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener {
-        AddSelectedAtEndAction() {
-            putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(selectionTableModel.getRowCount() > 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
-                memberTableModel.addMembersAtEnd(toAdd);
-            } catch (AddAbortException ex) {
-                // do nothing
-                if (Main.isTraceEnabled()) {
-                    Main.trace(ex.getMessage());
-                }
-            }
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
-        /**
-         * Constructs a new {@code AddSelectedBeforeSelection}.
-         */
-        AddSelectedBeforeSelection() {
-            putValue(SHORT_DESCRIPTION,
-                    tr("Add all objects selected in the current dataset before the first selected member"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(selectionTableModel.getRowCount() > 0
-                    && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
-                memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel
-                        .getSelectionModel().getMinSelectionIndex());
-            } catch (AddAbortException ex) {
-                // do nothing
-                if (Main.isTraceEnabled()) {
-                    Main.trace(ex.getMessage());
-                }
-            }
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            refreshEnabled();
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
-        AddSelectedAfterSelection() {
-            putValue(SHORT_DESCRIPTION,
-                    tr("Add all objects selected in the current dataset after the last selected member"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(selectionTableModel.getRowCount() > 0
-                    && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
-                memberTableModel.addMembersAfterIdx(toAdd, memberTableModel
-                        .getSelectionModel().getMaxSelectionIndex());
-            } catch (AddAbortException ex) {
-                // do nothing
-                if (Main.isTraceEnabled()) {
-                    Main.trace(ex.getMessage());
-                }
-            }
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            refreshEnabled();
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    class RemoveSelectedAction extends AbstractAction implements TableModelListener {
-        /**
-         * Constructs a new {@code RemoveSelectedAction}.
-         */
-        RemoveSelectedAction() {
-            putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
-            updateEnabledState();
-        }
-
-        protected void updateEnabledState() {
-            DataSet ds = getLayer().data;
-            if (ds == null || ds.getSelected().isEmpty()) {
-                setEnabled(false);
-                return;
-            }
-            // only enable the action if we have members referring to the
-            // selected primitives
-            //
-            setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    /**
-     * Selects  members in the relation editor which refer to primitives in the current
-     * selection of the context layer.
-     *
-     */
-    class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener {
-        SelectedMembersForSelectionAction() {
-            putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
-            updateEnabledState();
-        }
-
-        protected void updateEnabledState() {
-            boolean enabled = selectionTableModel.getRowCount() > 0
-            &&  !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty();
-
-            if (enabled) {
-                putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",
-                        memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size()));
-            } else {
-                putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
-            }
-            setEnabled(enabled);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.selectMembersReferringTo(getLayer().data.getSelected());
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    /**
-     * Selects primitives in the layer this editor belongs to. The selected primitives are
-     * equal to the set of primitives the currently selected relation members refer to.
-     *
-     */
-    class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener {
-        SelectPrimitivesForSelectedMembersAction() {
-            putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
-            updateEnabledState();
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTable.getSelectedRowCount() > 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives());
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class SortAction extends AbstractAction implements TableModelListener {
-        SortAction() {
-            String tooltip = tr("Sort the relation members");
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
-            putValue(NAME, tr("Sort"));
-            Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"),
-                KeyEvent.VK_END, Shortcut.ALT);
-            sc.setAccelerator(this);
-            putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
-            updateEnabledState();
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.sort();
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTableModel.getRowCount() > 0);
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class SortBelowAction extends AbstractAction implements TableModelListener, ListSelectionListener {
-        SortBelowAction() {
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort_below"));
-            putValue(NAME, tr("Sort below"));
-            putValue(SHORT_DESCRIPTION, tr("Sort the selected relation members and all members below"));
-            updateEnabledState();
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.sortBelow();
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTableModel.getRowCount() > 0 && !memberTableModel.getSelectionModel().isSelectionEmpty());
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class ReverseAction extends AbstractAction implements TableModelListener {
-        ReverseAction() {
-            putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse"));
-            putValue(NAME, tr("Reverse"));
-        //  Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"), KeyEvent.VK_END, Shortcut.ALT)
-            updateEnabledState();
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.reverse();
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTableModel.getRowCount() > 0);
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class MoveUpAction extends AbstractAction implements ListSelectionListener {
-        MoveUpAction() {
-            String tooltip = tr("Move the currently selected members up");
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
-            Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"),
-                KeyEvent.VK_UP, Shortcut.ALT);
-            sc.setAccelerator(this);
-            putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
-            setEnabled(false);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.moveUp(memberTable.getSelectedRows());
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
-        }
-    }
-
-    class MoveDownAction extends AbstractAction implements ListSelectionListener {
-        MoveDownAction() {
-            String tooltip = tr("Move the currently selected members down");
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
-            Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"),
-                KeyEvent.VK_DOWN, Shortcut.ALT);
-            sc.setAccelerator(this);
-            putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
-            setEnabled(false);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.moveDown(memberTable.getSelectedRows());
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
-        }
-    }
-
-    class RemoveAction extends AbstractAction implements ListSelectionListener {
-        RemoveAction() {
-            String tooltip = tr("Remove the currently selected members from this relation");
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
-            putValue(NAME, tr("Remove"));
-            Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"),
-                KeyEvent.VK_DELETE, Shortcut.ALT);
-            sc.setAccelerator(this);
-            putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
-            setEnabled(false);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTableModel.remove(memberTable.getSelectedRows());
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
-        }
-    }
-
-    class DeleteCurrentRelationAction extends AbstractAction implements PropertyChangeListener {
-        DeleteCurrentRelationAction() {
-            putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
-            putValue(NAME, tr("Delete"));
-            updateEnabledState();
-        }
-
-        public void run() {
-            Relation toDelete = getRelation();
-            if (toDelete == null)
-                return;
-            org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
-                    getLayer(),
-                    toDelete
-            );
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            run();
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(getRelationSnapshot() != null);
-        }
-
-        @Override
-        public void propertyChange(PropertyChangeEvent evt) {
-            if (evt.getPropertyName().equals(RELATION_SNAPSHOT_PROP)) {
-                updateEnabledState();
-            }
-        }
-    }
-
-    abstract class SavingAction extends AbstractAction {
-        /**
-         * apply updates to a new relation
-         */
-        protected void applyNewRelation() {
-            final Relation newRelation = new Relation();
-            tagEditorPanel.getModel().applyToPrimitive(newRelation);
-            memberTableModel.applyToRelation(newRelation);
-            List<RelationMember> newMembers = new ArrayList<>();
-            for (RelationMember rm: newRelation.getMembers()) {
-                if (!rm.getMember().isDeleted()) {
-                    newMembers.add(rm);
-                }
-            }
-            if (newRelation.getMembersCount() != newMembers.size()) {
-                newRelation.setMembers(newMembers);
-                String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" +
-                "was open. They have been removed from the relation members list.");
-                JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE);
-            }
-            // If the user wanted to create a new relation, but hasn't added any members or
-            // tags, don't add an empty relation
-            if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys())
-                return;
-            Main.main.undoRedo.add(new AddCommand(getLayer(), newRelation));
-
-            // make sure everybody is notified about the changes
-            //
-            getLayer().data.fireSelectionChanged();
-            GenericRelationEditor.this.setRelation(newRelation);
-            RelationDialogManager.getRelationDialogManager().updateContext(
-                    getLayer(),
-                    getRelation(),
-                    GenericRelationEditor.this
-            );
-            SwingUtilities.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    // Relation list gets update in EDT so selecting my be postponed to following EDT run
-                    Main.map.relationListDialog.selectRelation(newRelation);
-                }
-            });
-        }
-
-        /**
-         * Apply the updates for an existing relation which has been changed
-         * outside of the relation editor.
-         *
-         */
-        protected void applyExistingConflictingRelation() {
-            Relation editedRelation = new Relation(getRelation());
-            tagEditorPanel.getModel().applyToPrimitive(editedRelation);
-            memberTableModel.applyToRelation(editedRelation);
-            Conflict<Relation> conflict = new Conflict<>(getRelation(), editedRelation);
-            Main.main.undoRedo.add(new ConflictAddCommand(getLayer(), conflict));
-        }
-
-        /**
-         * Apply the updates for an existing relation which has not been changed
-         * outside of the relation editor.
-         *
-         */
-        protected void applyExistingNonConflictingRelation() {
-            Relation editedRelation = new Relation(getRelation());
-            tagEditorPanel.getModel().applyToPrimitive(editedRelation);
-            memberTableModel.applyToRelation(editedRelation);
-            Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation));
-            getLayer().data.fireSelectionChanged();
-            // this will refresh the snapshot and update the dialog title
-            //
-            setRelation(getRelation());
-        }
-
-        protected boolean confirmClosingBecauseOfDirtyState() {
-            ButtonSpec[] options = new ButtonSpec[] {
-                    new ButtonSpec(
-                            tr("Yes, create a conflict and close"),
-                            ImageProvider.get("ok"),
-                            tr("Click to create a conflict and close this relation editor"),
-                            null /* no specific help topic */
-                    ),
-                    new ButtonSpec(
-                            tr("No, continue editing"),
-                            ImageProvider.get("cancel"),
-                            tr("Click to return to the relation editor and to resume relation editing"),
-                            null /* no specific help topic */
-                    )
-            };
-
-            int ret = HelpAwareOptionPane.showOptionDialog(
-                    Main.parent,
-                    tr("<html>This relation has been changed outside of the editor.<br>"
-                            + "You cannot apply your changes and continue editing.<br>"
-                            + "<br>"
-                            + "Do you want to create a conflict and close the editor?</html>"),
-                            tr("Conflict in data"),
-                            JOptionPane.WARNING_MESSAGE,
-                            null,
-                            options,
-                            options[0], // OK is default
-                            "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
-            );
-            return ret == 0;
-        }
-
-        protected void warnDoubleConflict() {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("<html>Layer ''{0}'' already has a conflict for object<br>"
-                            + "''{1}''.<br>"
-                            + "Please resolve this conflict first, then try again.</html>",
-                            getLayer().getName(),
-                            getRelation().getDisplayName(DefaultNameFormatter.getInstance())
-                    ),
-                    tr("Double conflict"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-        }
-    }
-
-    class ApplyAction extends SavingAction {
-        ApplyAction() {
-            putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
-            putValue(SMALL_ICON, ImageProvider.get("save"));
-            putValue(NAME, tr("Apply"));
-            setEnabled(true);
-        }
-
-        public void run() {
-            if (getRelation() == null) {
-                applyNewRelation();
-            } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
-                    || tagEditorPanel.getModel().isDirty()) {
-                if (isDirtyRelation()) {
-                    if (confirmClosingBecauseOfDirtyState()) {
-                        if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
-                            warnDoubleConflict();
-                            return;
-                        }
-                        applyExistingConflictingRelation();
-                        setVisible(false);
-                    }
-                } else {
-                    applyExistingNonConflictingRelation();
-                }
-            }
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            run();
-        }
-    }
-
-    class OKAction extends SavingAction {
-        OKAction() {
-            putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
-            putValue(SMALL_ICON, ImageProvider.get("ok"));
-            putValue(NAME, tr("OK"));
-            setEnabled(true);
-        }
-
-        public void run() {
-            Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
-            memberTable.stopHighlighting();
-            if (getRelation() == null) {
-                applyNewRelation();
-            } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
-                    || tagEditorPanel.getModel().isDirty()) {
-                if (isDirtyRelation()) {
-                    if (confirmClosingBecauseOfDirtyState()) {
-                        if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
-                            warnDoubleConflict();
-                            return;
-                        }
-                        applyExistingConflictingRelation();
-                    } else
-                        return;
-                } else {
-                    applyExistingNonConflictingRelation();
-                }
-            }
-            setVisible(false);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            run();
-        }
-    }
-
-    class CancelAction extends SavingAction {
-        CancelAction() {
-            putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
-            putValue(SMALL_ICON, ImageProvider.get("cancel"));
-            putValue(NAME, tr("Cancel"));
-
-            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
-            .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
-            getRootPane().getActionMap().put("ESCAPE", this);
-            setEnabled(true);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            memberTable.stopHighlighting();
-            TagEditorModel tagModel = tagEditorPanel.getModel();
-            Relation snapshot = getRelationSnapshot();
-            if ((!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty())
-             && !(snapshot == null && tagModel.getTags().isEmpty())) {
-                //give the user a chance to save the changes
-                int ret = confirmClosingByCancel();
-                if (ret == 0) { //Yes, save the changes
-                    //copied from OKAction.run()
-                    Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
-                    if (getRelation() == null) {
-                        applyNewRelation();
-                    } else if (!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty()) {
-                        if (isDirtyRelation()) {
-                            if (confirmClosingBecauseOfDirtyState()) {
-                                if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
-                                    warnDoubleConflict();
-                                    return;
-                                }
-                                applyExistingConflictingRelation();
-                            } else
-                                return;
-                        } else {
-                            applyExistingNonConflictingRelation();
-                        }
-                    }
-                } else if (ret == 2) //Cancel, continue editing
-                    return;
-                //in case of "No, discard", there is no extra action to be performed here.
-            }
-            setVisible(false);
-        }
-
-        protected int confirmClosingByCancel() {
-            ButtonSpec[] options = new ButtonSpec[] {
-                    new ButtonSpec(
-                            tr("Yes, save the changes and close"),
-                            ImageProvider.get("ok"),
-                            tr("Click to save the changes and close this relation editor"),
-                            null /* no specific help topic */
-                    ),
-                    new ButtonSpec(
-                            tr("No, discard the changes and close"),
-                            ImageProvider.get("cancel"),
-                            tr("Click to discard the changes and close this relation editor"),
-                            null /* no specific help topic */
-                    ),
-                    new ButtonSpec(
-                            tr("Cancel, continue editing"),
-                            ImageProvider.get("cancel"),
-                            tr("Click to return to the relation editor and to resume relation editing"),
-                            null /* no specific help topic */
-                    )
-            };
-
-            return HelpAwareOptionPane.showOptionDialog(
-                    Main.parent,
-                    tr("<html>The relation has been changed.<br>"
-                            + "<br>"
-                            + "Do you want to save your changes?</html>"),
-                            tr("Unsaved changes"),
-                            JOptionPane.WARNING_MESSAGE,
-                            null,
-                            options,
-                            options[0], // OK is default,
-                            "/Dialog/RelationEditor#DiscardChanges"
-            );
-        }
-    }
-
-    class AddTagAction extends AbstractAction {
-        AddTagAction() {
-            putValue(SHORT_DESCRIPTION, tr("Add an empty tag"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
-            setEnabled(true);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            tagEditorPanel.getModel().appendNewTag();
-        }
-    }
-
-    class DownloadIncompleteMembersAction extends AbstractAction implements TableModelListener {
-        DownloadIncompleteMembersAction() {
-            String tooltip = tr("Download all incomplete members");
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete"));
-            putValue(NAME, tr("Download Members"));
-            Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
-                KeyEvent.VK_HOME, Shortcut.ALT);
-            sc.setAccelerator(this);
-            putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
-            updateEnabledState();
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (!isEnabled())
-                return;
-            Main.worker.submit(new DownloadRelationMemberTask(
-                    getRelation(),
-                    memberTableModel.getIncompleteMemberPrimitives(),
-                    getLayer(),
-                    GenericRelationEditor.this)
-            );
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTableModel.hasIncompleteMembers() && !Main.isOffline(OnlineResource.OSM_API));
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener, TableModelListener {
-        DownloadSelectedIncompleteMembersAction() {
-            putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
-            putValue(NAME, tr("Download Members"));
-        //  Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"), KeyEvent.VK_K, Shortcut.ALT)
-            updateEnabledState();
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (!isEnabled())
-                return;
-            Main.worker.submit(new DownloadRelationMemberTask(
-                    getRelation(),
-                    memberTableModel.getSelectedIncompleteMemberPrimitives(),
-                    getLayer(),
-                    GenericRelationEditor.this)
-            );
-        }
-
-        protected void updateEnabledState() {
-            setEnabled(memberTableModel.hasIncompleteSelectedMembers() && !Main.isOffline(OnlineResource.OSM_API));
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            updateEnabledState();
-        }
-
-        @Override
-        public void tableChanged(TableModelEvent e) {
-            updateEnabledState();
-        }
-    }
-
-    class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener {
-        SetRoleAction() {
-            putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
-            putValue(SMALL_ICON, ImageProvider.get("apply"));
-            putValue(NAME, tr("Apply Role"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(memberTable.getSelectedRowCount() > 0);
-        }
-
-        protected boolean isEmptyRole() {
-            return tfRole.getText() == null || tfRole.getText().trim().isEmpty();
-        }
-
-        protected boolean confirmSettingEmptyRole(int onNumMembers) {
-            String message = "<html>"
-                + trn("You are setting an empty role on {0} object.",
-                        "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers)
-                        + "<br>"
-                        + tr("This is equal to deleting the roles of these objects.") +
-                        "<br>"
-                        + tr("Do you really want to apply the new role?") + "</html>";
-            String[] options = new String[] {
-                    tr("Yes, apply it"),
-                    tr("No, do not apply")
-            };
-            int ret = ConditionalOptionPaneUtil.showOptionDialog(
-                    "relation_editor.confirm_applying_empty_role",
-                    Main.parent,
-                    message,
-                    tr("Confirm empty role"),
-                    JOptionPane.YES_NO_OPTION,
-                    JOptionPane.WARNING_MESSAGE,
-                    options,
-                    options[0]
-            );
-            switch(ret) {
-            case JOptionPane.YES_OPTION:
-            case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
-                return true;
-            default:
-                return false;
-            }
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (isEmptyRole()) {
-                if (!confirmSettingEmptyRole(memberTable.getSelectedRowCount()))
-                    return;
-            }
-            memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            refreshEnabled();
-        }
-
-        @Override
-        public void changedUpdate(DocumentEvent e) {
-            refreshEnabled();
-        }
-
-        @Override
-        public void insertUpdate(DocumentEvent e) {
-            refreshEnabled();
-        }
-
-        @Override
-        public void removeUpdate(DocumentEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    /**
-     * Creates a new relation with a copy of the current editor state.
-     */
-    class DuplicateRelationAction extends AbstractAction {
-        DuplicateRelationAction() {
-            putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
-            // FIXME provide an icon
-            putValue(SMALL_ICON, ImageProvider.get("duplicate"));
-            putValue(NAME, tr("Duplicate"));
-            setEnabled(true);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            Relation copy = new Relation();
-            tagEditorPanel.getModel().applyToPrimitive(copy);
-            memberTableModel.applyToRelation(copy);
-            RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers());
-            editor.setVisible(true);
-        }
-    }
-
-    /**
-     * Action for editing the currently selected relation.
-     */
-    class EditAction extends AbstractAction implements ListSelectionListener {
-        EditAction() {
-            putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
-            putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
-            refreshEnabled();
-        }
-
-        protected void refreshEnabled() {
-            setEnabled(memberTable.getSelectedRowCount() == 1
-                    && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
-        }
-
-        protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
-            Collection<RelationMember> members = new HashSet<>();
-            Collection<OsmPrimitive> selection = getLayer().data.getSelected();
-            for (RelationMember member: r.getMembers()) {
-                if (selection.contains(member.getMember())) {
-                    members.add(member);
-                }
-            }
-            return members;
-        }
-
-        public void run() {
-            int idx = memberTable.getSelectedRow();
-            if (idx < 0)
-                return;
-            OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
-            if (!(primitive instanceof Relation))
-                return;
-            Relation r = (Relation) primitive;
-            if (r.isIncomplete())
-                return;
-
-            RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r));
-            editor.setVisible(true);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (!isEnabled())
-                return;
-            run();
-        }
-
-        @Override
-        public void valueChanged(ListSelectionEvent e) {
-            refreshEnabled();
-        }
-    }
-
-    class PasteMembersAction extends AddFromSelectionAction {
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
-                DataSet ds = getLayer().data;
-                List<OsmPrimitive> toAdd = new ArrayList<>();
-                boolean hasNewInOtherLayer = false;
-
-                for (PrimitiveData primitive: primitives) {
-                    OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
-                    if (primitiveInDs != null) {
-                        toAdd.add(primitiveInDs);
-                    } else if (!primitive.isNew()) {
-                        OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
-                        ds.addPrimitive(p);
-                        toAdd.add(p);
-                    } else {
-                        hasNewInOtherLayer = true;
-                        break;
-                    }
-                }
-
-                if (hasNewInOtherLayer) {
-                    JOptionPane.showMessageDialog(Main.parent,
-                            tr("Members from paste buffer cannot be added because they are not included in current layer"));
-                    return;
-                }
-
-                toAdd = filterConfirmedPrimitives(toAdd);
-                int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
-                if (index == -1) {
-                    index = memberTableModel.getRowCount() - 1;
-                }
-                memberTableModel.addMembersAfterIdx(toAdd, index);
-
-                tfRole.requestFocusInWindow();
-
-            } catch (AddAbortException ex) {
-                // Do nothing
-                if (Main.isTraceEnabled()) {
-                    Main.trace(ex.getMessage());
-                }
-            }
-        }
-    }
-
-    class CopyMembersAction extends AbstractAction {
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            Set<OsmPrimitive> primitives = new HashSet<>();
-            for (RelationMember rm: memberTableModel.getSelectedMembers()) {
-                primitives.add(rm.getMember());
-            }
-            if (!primitives.isEmpty()) {
-                CopyAction.copy(getLayer(), primitives);
-            }
-        }
-    }
-
     class MemberTableDblClickAdapter extends MouseAdapter {
         @Override
         public void mouseClicked(MouseEvent e) {
             if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
-                new EditAction().run();
+                new EditAction(memberTable, memberTableModel, getLayer()).actionPerformed(null);
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 9493)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 9496)
@@ -703,7 +703,7 @@
      * Sort the selected relation members by the way they are linked.
      */
-    void sort() {
+    public void sort() {
         List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers());
-        List<RelationMember> sortedMembers = null;
+        List<RelationMember> sortedMembers;
         List<RelationMember> newMembers;
         if (selectedMembers.size() <= 1) {
@@ -727,5 +727,6 @@
         }
 
-        if (members.size() != newMembers.size()) throw new AssertionError();
+        if (members.size() != newMembers.size())
+            throw new AssertionError();
 
         members.clear();
@@ -738,6 +739,6 @@
      * Sort the selected relation members and all members below by the way they are linked.
      */
-    void sortBelow() {
-        final List<RelationMember> subList = members.subList(getSelectionModel().getMinSelectionIndex(), members.size());
+    public void sortBelow() {
+        final List<RelationMember> subList = members.subList(Math.max(0, getSelectionModel().getMinSelectionIndex()), members.size());
         final List<RelationMember> sorted = relationSorter.sortMembers(subList);
         subList.clear();
@@ -766,5 +767,5 @@
      * Reverse the relation members.
      */
-    void reverse() {
+    public void reverse() {
         List<Integer> selectedIndices = getSelectedIndices();
         List<Integer> selectedIndicesReversed = getSelectedIndices();
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationAware.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationAware.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationAware.java	(revision 9496)
@@ -0,0 +1,42 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation;
+
+import org.openstreetmap.josm.data.osm.Relation;
+
+/**
+ * Super interface of relation-aware editors.
+ * @since 9496
+ */
+public interface RelationAware {
+
+    /**
+     * Replies the currently edited relation
+     *
+     * @return the currently edited relation
+     */
+    Relation getRelation();
+
+    /**
+     * Sets the currently edited relation. Creates a snapshot of the current
+     * state of the relation. See {@link #getRelationSnapshot()}
+     *
+     * @param relation the relation
+     */
+    void setRelation(Relation relation);
+
+    /**
+     * Replies the state of the edited relation when the editor has been launched.
+     * @return the state of the edited relation when the editor has been launched
+     */
+    Relation getRelationSnapshot();
+
+    /**
+     * Replies true if the currently edited relation has been changed elsewhere.
+     *
+     * In this case a relation editor can't apply updates to the relation directly. Rather,
+     * it has to create a conflict.
+     *
+     * @return true if the currently edited relation has been changed elsewhere.
+     */
+    boolean isDirtyRelation();
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java	(revision 9493)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java	(revision 9496)
@@ -19,5 +19,10 @@
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
-public abstract class RelationEditor extends ExtendedDialog {
+/**
+ * Abstract relation editor.
+ * @since 1599
+ */
+public abstract class RelationEditor extends ExtendedDialog implements RelationAware {
+
     /** the property name for the current relation.
      * @see #setRelation(Relation)
@@ -34,4 +39,34 @@
     private static List<Class<RelationEditor>> editors = new ArrayList<>();
 
+    /** The relation that this editor is working on. */
+    private transient Relation relation;
+
+    /** The version of the relation when editing is started. This is null if a new relation is created. */
+    private transient Relation relationSnapshot;
+
+    /** The data layer the relation belongs to */
+    private final transient OsmDataLayer layer;
+
+    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+    /**
+     * Creates a new relation editor
+     *
+     * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
+     * @param relation the relation. Can be null if a new relation is to be edited.
+     * @throws IllegalArgumentException if layer is null
+     */
+    protected RelationEditor(OsmDataLayer layer, Relation relation) {
+        super(Main.parent,
+                "",
+                new String[] {tr("Apply Changes"), tr("Cancel")},
+                false,
+                false
+        );
+        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
+        this.layer = layer;
+        setRelation(relation);
+    }
+
     /**
      * Registers a relation editor class. Depending on the type of relation to be edited
@@ -42,6 +77,5 @@
      */
     public void registerRelationEditor(Class<RelationEditor> clazz) {
-        if (clazz == null) return;
-        if (!editors.contains(clazz)) {
+        if (clazz != null && !editors.contains(clazz)) {
             editors.add(clazz);
         }
@@ -49,35 +83,17 @@
 
     /**
-     * The relation that this editor is working on.
-     */
-    private transient Relation relation;
-
-    /**
-     * The version of the relation when editing is started.  This is
-     * null if a new relation is created. */
-    private transient Relation relationSnapshot;
-
-    /** the data layer the relation belongs to */
-    private final transient OsmDataLayer layer;
-
-    /**
-     * This is a factory method that creates an appropriate RelationEditor
-     * instance suitable for editing the relation that was passed in as an
-     * argument.
+     * This is a factory method that creates an appropriate RelationEditor instance suitable for editing the relation
+     * that was passed in as an argument.
      *
-     * This method is guaranteed to return a working RelationEditor. If no
-     * specific editor has been registered for the type of relation, then
-     * a generic editor will be returned.
+     * This method is guaranteed to return a working RelationEditor. If no specific editor has been registered for the
+     * type of relation, then a generic editor will be returned.
      *
-     * Editors can be registered by adding their class to the static list "editors"
-     * in the RelationEditor class. When it comes to editing a relation, all
-     * registered editors are queried via their static "canEdit" method whether they
-     * feel responsible for that kind of relation, and if they return true
-     * then an instance of that class will be used.
+     * Editors can be registered by adding their class to the static list "editors" in the RelationEditor class.
+     * When it comes to editing a relation, all registered editors are queried via their static "canEdit" method whether
+     * they feel responsible for that kind of relation, and if they return true then an instance of that class will be used.
      *
      * @param layer the data layer the relation is a member of
      * @param r the relation to be edited
-     * @param selectedMembers a collection of relation members which shall be selected when the
-     * editor is first launched
+     * @param selectedMembers a collection of relation members which shall be selected when the editor is first launched
      * @return an instance of RelationEditor suitable for editing that kind of relation
      */
@@ -106,25 +122,4 @@
 
     /**
-     * Creates a new relation editor
-     *
-     * @param layer  the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
-     * @param relation the relation. Can be null if a new relation is to be edited.
-     * @param selectedMembers  a collection of members in <code>relation</code> which the editor
-     * should display selected when the editor is first displayed on screen
-     * @throws IllegalArgumentException if layer is null
-     */
-    protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
-        super(Main.parent,
-                "",
-                new String[] {tr("Apply Changes"), tr("Cancel")},
-                false,
-                false
-        );
-        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
-        this.layer = layer;
-        setRelation(relation);
-    }
-
-    /**
      * updates the title of the relation editor
      */
@@ -139,20 +134,11 @@
     }
 
-    /**
-     * Replies the currently edited relation
-     *
-     * @return the currently edited relation
-     */
-    protected Relation getRelation() {
+    @Override
+    public final Relation getRelation() {
         return relation;
     }
 
-    /**
-     * Sets the currently edited relation. Creates a snapshot of the current
-     * state of the relation. See {@link #getRelationSnapshot()}
-     *
-     * @param relation the relation
-     */
-    protected void setRelation(Relation relation) {
+    @Override
+    public final void setRelation(Relation relation) {
         setRelationSnapshot((relation == null) ? null : new Relation(relation));
         Relation oldValue = this.relation;
@@ -165,24 +151,18 @@
 
     /**
-     * Replies the {@link OsmDataLayer} in whose context this relation editor is
-     * open
+     * Replies the {@link OsmDataLayer} in whose context this relation editor is open
      *
-     * @return the {@link OsmDataLayer} in whose context this relation editor is
-     * open
+     * @return the {@link OsmDataLayer} in whose context this relation editor is open
      */
-    protected OsmDataLayer getLayer() {
+    protected final OsmDataLayer getLayer() {
         return layer;
     }
 
-    /**
-     * Replies the state of the edited relation when the editor has been launched
-     *
-     * @return the state of the edited relation when the editor has been launched
-     */
-    protected Relation getRelationSnapshot() {
+    @Override
+    public final Relation getRelationSnapshot() {
         return relationSnapshot;
     }
 
-    protected void setRelationSnapshot(Relation snapshot) {
+    protected final void setRelationSnapshot(Relation snapshot) {
         Relation oldValue = relationSnapshot;
         relationSnapshot = snapshot;
@@ -192,13 +172,6 @@
     }
 
-    /**
-     * Replies true if the currently edited relation has been changed elsewhere.
-     *
-     * In this case a relation editor can't apply updates to the relation directly. Rather,
-     * it has to create a conflict.
-     *
-     * @return true if the currently edited relation has been changed elsewhere.
-     */
-    protected boolean isDirtyRelation() {
+    @Override
+    public final boolean isDirtyRelation() {
         return !relation.hasEqualSemanticAttributes(relationSnapshot);
     }
@@ -207,13 +180,12 @@
     /* property change support                                                 */
     /* ----------------------------------------------------------------------- */
-    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
 
     @Override
-    public void addPropertyChangeListener(PropertyChangeListener listener) {
+    public final void addPropertyChangeListener(PropertyChangeListener listener) {
         this.support.addPropertyChangeListener(listener);
     }
 
     @Override
-    public void removePropertyChangeListener(PropertyChangeListener listener) {
+    public final void removePropertyChangeListener(PropertyChangeListener listener) {
         this.support.removePropertyChangeListener(listener);
     }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorAction.java	(revision 9496)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation.actions;
+
+import javax.swing.AbstractAction;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * Abstract superclass of relation editor actions.
+ * @since 9496
+ */
+abstract class AbstractRelationEditorAction extends AbstractAction implements TableModelListener, ListSelectionListener {
+    protected final MemberTable memberTable;
+    protected final MemberTableModel memberTableModel;
+    protected final transient OsmDataLayer layer;
+    protected final transient RelationAware editor;
+
+    protected AbstractRelationEditorAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey) {
+        this(memberTable, memberTableModel, actionMapKey, null, null);
+    }
+
+    protected AbstractRelationEditorAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey,
+            OsmDataLayer layer, RelationAware editor) {
+        this.memberTable = memberTable;
+        this.memberTableModel = memberTableModel;
+        this.layer = layer;
+        this.editor = editor;
+        if (actionMapKey != null) {
+            this.memberTable.getActionMap().put(actionMapKey, this);
+        }
+    }
+
+    @Override
+    public void tableChanged(TableModelEvent e) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void valueChanged(ListSelectionEvent e) {
+        updateEnabledState();
+    }
+
+    protected abstract void updateEnabledState();
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java	(revision 9496)
@@ -0,0 +1,62 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation.actions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTable;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * Abstract superclass of "Add from selection" actions.
+ * @since 9496
+ */
+abstract class AddFromSelectionAction extends AbstractRelationEditorAction {
+
+    protected final SelectionTable selectionTable;
+    protected final SelectionTableModel selectionTableModel;
+
+    protected AddFromSelectionAction(MemberTable memberTable, MemberTableModel memberTableModel, SelectionTable selectionTable,
+            SelectionTableModel selectionTableModel, String actionMapKey, OsmDataLayer layer, RelationAware editor) {
+        super(memberTable, memberTableModel, actionMapKey, layer, editor);
+        this.selectionTable = selectionTable;
+        this.selectionTableModel = selectionTableModel;
+    }
+
+    protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
+        return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
+    }
+
+    protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
+        if (primitives == null || primitives.isEmpty())
+            return primitives;
+        List<OsmPrimitive> ret = new ArrayList<>();
+        ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
+        for (OsmPrimitive primitive : primitives) {
+            if (primitive instanceof Relation && editor.getRelation() != null && editor.getRelation().equals(primitive)) {
+                GenericRelationEditor.warnOfCircularReferences(primitive);
+                continue;
+            }
+            if (isPotentialDuplicate(primitive)) {
+                if (GenericRelationEditor.confirmAddingPrimitive(primitive)) {
+                    ret.add(primitive);
+                }
+                continue;
+            } else {
+                ret.add(primitive);
+            }
+        }
+        ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
+        return ret;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAfterSelection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAfterSelection.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAfterSelection.java	(revision 9496)
@@ -0,0 +1,50 @@
+// 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 org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Add all objects selected in the current dataset after the last selected member.
+ * @since 9496
+ */
+public class AddSelectedAfterSelection extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code AddSelectedAfterSelection}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param editor relation editor
+     */
+    public AddSelectedAfterSelection(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, RelationAware editor) {
+        super(null, memberTableModel, null, selectionTableModel, null, null, editor);
+        putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last selected member"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(selectionTableModel.getRowCount() > 0 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            memberTableModel.addMembersAfterIdx(filterConfirmedPrimitives(selectionTableModel.getSelection()),
+                    memberTableModel.getSelectionModel().getMaxSelectionIndex());
+        } catch (AddAbortException ex) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(ex.getMessage());
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtEndAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtEndAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtEndAction.java	(revision 9496)
@@ -0,0 +1,49 @@
+// 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 org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Add all objects selected in the current dataset after the last member.
+ * @since 9496
+ */
+public class AddSelectedAtEndAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code AddSelectedAtEndAction}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param editor relation editor
+     */
+    public AddSelectedAtEndAction(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, RelationAware editor) {
+        super(null, memberTableModel, null, selectionTableModel, null, null, editor);
+        putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(selectionTableModel.getRowCount() > 0);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            memberTableModel.addMembersAtEnd(filterConfirmedPrimitives(selectionTableModel.getSelection()));
+        } catch (AddAbortException ex) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(ex.getMessage());
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtStartAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtStartAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedAtStartAction.java	(revision 9496)
@@ -0,0 +1,49 @@
+// 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 org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Add all objects selected in the current dataset before the first member.
+ * @since 9496
+ */
+public class AddSelectedAtStartAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code AddSelectedAtStartAction}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param editor relation editor
+     */
+    public AddSelectedAtStartAction(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, RelationAware editor) {
+        super(null, memberTableModel, null, selectionTableModel, null, null, editor);
+        putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset before the first member"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(selectionTableModel.getRowCount() > 0);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            memberTableModel.addMembersAtBeginning(filterConfirmedPrimitives(selectionTableModel.getSelection()));
+        } catch (AddAbortException ex) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(ex.getMessage());
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedBeforeSelection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedBeforeSelection.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddSelectedBeforeSelection.java	(revision 9496)
@@ -0,0 +1,50 @@
+// 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 org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Add all objects selected in the current dataset before the first selected member.
+ * @since 9496
+ */
+public class AddSelectedBeforeSelection extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code AddSelectedBeforeSelection}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param editor relation editor
+     */
+    public AddSelectedBeforeSelection(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, RelationAware editor) {
+        super(null, memberTableModel, null, selectionTableModel, null, null, editor);
+        putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset before the first selected member"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(selectionTableModel.getRowCount() > 0 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            memberTableModel.addMembersBeforeIdx(filterConfirmedPrimitives(selectionTableModel.getSelection()),
+                    memberTableModel.getSelectionModel().getMinSelectionIndex());
+        } catch (AddAbortException ex) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(ex.getMessage());
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java	(revision 9496)
@@ -0,0 +1,60 @@
+// 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.Component;
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Apply the current updates.
+ * @since 9496
+ */
+public class ApplyAction extends SavingAction {
+
+    /**
+     * Constructs a new {@code ApplyAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param layer OSM data layer
+     * @param editor relation editor
+     * @param tagModel tag editor model
+     */
+    public ApplyAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer,
+            RelationAware editor) {
+        super(memberTable, memberTableModel, tagModel, layer, editor, null);
+        putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
+        putValue(SMALL_ICON, ImageProvider.get("save"));
+        putValue(NAME, tr("Apply"));
+        setEnabled(true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (editor.getRelation() == null) {
+            applyNewRelation(tagModel);
+        } else if (!memberTableModel.hasSameMembersAs(editor.getRelationSnapshot()) || tagModel.isDirty()) {
+            if (editor.isDirtyRelation()) {
+                if (confirmClosingBecauseOfDirtyState()) {
+                    if (layer.getConflicts().hasConflictForMy(editor.getRelation())) {
+                        warnDoubleConflict();
+                        return;
+                    }
+                    applyExistingConflictingRelation(tagModel);
+                    if (editor instanceof Component) {
+                        ((Component) editor).setVisible(false);
+                    }
+                }
+            } else {
+                applyExistingNonConflictingRelation(tagModel);
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java	(revision 9496)
@@ -0,0 +1,126 @@
+// 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.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
+import javax.swing.RootPaneContainer;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Cancel the updates and close the dialog
+ * @since 9496
+ */
+public class CancelAction extends SavingAction {
+
+    /**
+     * Constructs a new {@code CancelAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param tagModel tag editor model
+     * @param layer OSM data layer
+     * @param editor relation editor
+     * @param tfRole role text field
+     */
+    public CancelAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer,
+            RelationAware editor, AutoCompletingTextField tfRole) {
+        super(memberTable, memberTableModel, tagModel, layer, editor, tfRole);
+        putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
+        putValue(SMALL_ICON, ImageProvider.get("cancel"));
+        putValue(NAME, tr("Cancel"));
+
+        if (editor instanceof RootPaneContainer) {
+            JRootPane root = ((RootPaneContainer) editor).getRootPane();
+            root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
+            root.getActionMap().put("ESCAPE", this);
+        }
+        setEnabled(true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTable.stopHighlighting();
+        Relation snapshot = editor.getRelationSnapshot();
+        if ((!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty())
+         && !(snapshot == null && tagModel.getTags().isEmpty())) {
+            //give the user a chance to save the changes
+            int ret = confirmClosingByCancel();
+            if (ret == 0) { //Yes, save the changes
+                //copied from OKAction.run()
+                Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
+                if (editor.getRelation() == null) {
+                    applyNewRelation(tagModel);
+                } else if (!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty()) {
+                    if (editor.isDirtyRelation()) {
+                        if (confirmClosingBecauseOfDirtyState()) {
+                            if (layer.getConflicts().hasConflictForMy(editor.getRelation())) {
+                                warnDoubleConflict();
+                                return;
+                            }
+                            applyExistingConflictingRelation(tagModel);
+                        } else
+                            return;
+                    } else {
+                        applyExistingNonConflictingRelation(tagModel);
+                    }
+                }
+            } else if (ret == 2) //Cancel, continue editing
+                return;
+            //in case of "No, discard", there is no extra action to be performed here.
+        }
+        if (editor instanceof Component) {
+            ((Component) editor).setVisible(false);
+        }
+    }
+
+    protected int confirmClosingByCancel() {
+        ButtonSpec[] options = new ButtonSpec[] {
+                new ButtonSpec(
+                        tr("Yes, save the changes and close"),
+                        ImageProvider.get("ok"),
+                        tr("Click to save the changes and close this relation editor"),
+                        null /* no specific help topic */
+                ),
+                new ButtonSpec(
+                        tr("No, discard the changes and close"),
+                        ImageProvider.get("cancel"),
+                        tr("Click to discard the changes and close this relation editor"),
+                        null /* no specific help topic */
+                ),
+                new ButtonSpec(
+                        tr("Cancel, continue editing"),
+                        ImageProvider.get("cancel"),
+                        tr("Click to return to the relation editor and to resume relation editing"),
+                        null /* no specific help topic */
+                )
+        };
+
+        return HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                tr("<html>The relation has been changed.<br><br>Do you want to save your changes?</html>"),
+                        tr("Unsaved changes"),
+                        JOptionPane.WARNING_MESSAGE,
+                        null,
+                        options,
+                        options[0], // OK is default,
+                        "/Dialog/RelationEditor#DiscardChanges"
+        );
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java	(revision 9496)
@@ -0,0 +1,46 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation.actions;
+
+import java.awt.event.ActionEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.openstreetmap.josm.actions.CopyAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * Copy members.
+ * @since 9496
+ */
+public class CopyMembersAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code CopyMembersAction}.
+     * @param memberTableModel member table model
+     * @param layer OSM data layer
+     * @param editor relation editor
+     */
+    public CopyMembersAction(MemberTableModel memberTableModel, OsmDataLayer layer, RelationAware editor) {
+        super(null, memberTableModel, null, null, null, layer, editor);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Set<OsmPrimitive> primitives = new HashSet<>();
+        for (RelationMember rm: memberTableModel.getSelectedMembers()) {
+            primitives.add(rm.getMember());
+        }
+        if (!primitives.isEmpty()) {
+            CopyAction.copy(layer, primitives);
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        // Do nothing
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java	(revision 9496)
@@ -0,0 +1,55 @@
+// 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.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import org.openstreetmap.josm.actions.mapmode.DeleteAction;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Delete the currently edited relation.
+ * @since 9496
+ */
+public class DeleteCurrentRelationAction extends AbstractRelationEditorAction implements PropertyChangeListener {
+
+    /**
+     * Constructs a new {@code DeleteCurrentRelationAction}.
+     * @param layer OSM data layer
+     * @param editor relation editor
+     */
+    public DeleteCurrentRelationAction(OsmDataLayer layer, RelationAware editor) {
+        super(null, null, null, layer, editor);
+        putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+        putValue(NAME, tr("Delete"));
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Relation toDelete = editor.getRelation();
+        if (toDelete == null)
+            return;
+        DeleteAction.deleteRelation(layer, toDelete);
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(editor.getRelationSnapshot() != null);
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (GenericRelationEditor.RELATION_SNAPSHOT_PROP.equals(evt.getPropertyName())) {
+            updateEnabledState();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadIncompleteMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadIncompleteMembersAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadIncompleteMembersAction.java	(revision 9496)
@@ -0,0 +1,62 @@
+// 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.Dialog;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.OnlineResource;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Download all incomplete members.
+ * @since 9496
+ */
+public class DownloadIncompleteMembersAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code DownloadIncompleteMembersAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param actionMapKey action map key
+     * @param layer OSM data layer
+     * @param editor relation editor
+     */
+    public DownloadIncompleteMembersAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey,
+            OsmDataLayer layer, RelationAware editor) {
+        super(memberTable, memberTableModel, actionMapKey, layer, editor);
+        Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
+            KeyEvent.VK_HOME, Shortcut.ALT);
+        sc.setAccelerator(this);
+        putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tr("Download all incomplete members"), sc));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete"));
+        putValue(NAME, tr("Download Members"));
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (!isEnabled())
+            return;
+        Main.worker.submit(new DownloadRelationMemberTask(
+                editor.getRelation(),
+                memberTableModel.getIncompleteMemberPrimitives(),
+                layer,
+                (Dialog) editor)
+        );
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.hasIncompleteMembers() && !Main.isOffline(OnlineResource.OSM_API));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadSelectedIncompleteMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadSelectedIncompleteMembersAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DownloadSelectedIncompleteMembersAction.java	(revision 9496)
@@ -0,0 +1,59 @@
+// 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.Dialog;
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.OnlineResource;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Download selected incomplete members.
+ * @since 9496
+ */
+public class DownloadSelectedIncompleteMembersAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code DownloadSelectedIncompleteMembersAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param actionMapKey action map key
+     * @param layer OSM data layer
+     * @param editor relation editor
+     */
+    public DownloadSelectedIncompleteMembersAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey,
+            OsmDataLayer layer, RelationAware editor) {
+        super(memberTable, memberTableModel, actionMapKey, layer, editor);
+        //  Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
+            // KeyEvent.VK_K, Shortcut.ALT)
+        putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
+        putValue(NAME, tr("Download Members"));
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (!isEnabled())
+            return;
+        Main.worker.submit(new DownloadRelationMemberTask(
+                editor.getRelation(),
+                memberTableModel.getSelectedIncompleteMemberPrimitives(),
+                layer,
+                (Dialog) editor)
+        );
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.hasIncompleteSelectedMembers() && !Main.isOffline(OnlineResource.OSM_API));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DuplicateRelationAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DuplicateRelationAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DuplicateRelationAction.java	(revision 9496)
@@ -0,0 +1,54 @@
+// 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.GraphicsEnvironment;
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Creates a new relation with a copy of the current editor state.
+ * @since 9496
+ */
+public class DuplicateRelationAction extends AbstractRelationEditorAction {
+
+    private final transient TagEditorModel tagEditorModel;
+
+    /**
+     * Constructs a new {@code DuplicateRelationAction}.
+     * @param memberTableModel member table model
+     * @param tagEditorModel tag editor model
+     * @param layer OSM data layer
+     */
+    public DuplicateRelationAction(MemberTableModel memberTableModel, TagEditorModel tagEditorModel, OsmDataLayer layer) {
+        super(null, memberTableModel, null, layer, null);
+        this.tagEditorModel = tagEditorModel;
+        putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
+        // FIXME provide an icon
+        putValue(SMALL_ICON, ImageProvider.get("duplicate"));
+        putValue(NAME, tr("Duplicate"));
+        setEnabled(true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Relation copy = new Relation();
+        tagEditorModel.applyToPrimitive(copy);
+        memberTableModel.applyToRelation(copy);
+        if (!GraphicsEnvironment.isHeadless()) {
+            RelationEditor.getEditor(layer, copy, memberTableModel.getSelectedMembers()).setVisible(true);
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        // Do nothing
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/EditAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/EditAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/EditAction.java	(revision 9496)
@@ -0,0 +1,71 @@
+// 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.Collection;
+import java.util.HashSet;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Action for editing the currently selected relation.
+ * @since 9496
+ */
+public class EditAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code EditAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param layer layer
+     */
+    public EditAction(MemberTable memberTable, MemberTableModel memberTableModel, OsmDataLayer layer) {
+        super(memberTable, memberTableModel, null, layer, null);
+        putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTable.getSelectedRowCount() == 1
+                && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
+    }
+
+    protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
+        Collection<RelationMember> members = new HashSet<>();
+        Collection<OsmPrimitive> selection = layer.data.getSelected();
+        for (RelationMember member: r.getMembers()) {
+            if (selection.contains(member.getMember())) {
+                members.add(member);
+            }
+        }
+        return members;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (!isEnabled())
+            return;
+        int idx = memberTable.getSelectedRow();
+        if (idx < 0)
+            return;
+        OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
+        if (!(primitive instanceof Relation))
+            return;
+        Relation r = (Relation) primitive;
+        if (r.isIncomplete())
+            return;
+
+        RelationEditor.getEditor(layer, r, getMembersForCurrentSelection(r)).setVisible(true);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveDownAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveDownAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveDownAction.java	(revision 9496)
@@ -0,0 +1,45 @@
+// 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.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Move the currently selected members down.
+ * @since 9496
+ */
+public class MoveDownAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code MoveDownAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param actionMapKey action map key
+     */
+    public MoveDownAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey) {
+        super(memberTable, memberTableModel, actionMapKey);
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
+        Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"), KeyEvent.VK_DOWN, Shortcut.ALT);
+        sc.setAccelerator(this);
+        putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tr("Move the currently selected members down"), sc));
+        setEnabled(false);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.moveDown(memberTable.getSelectedRows());
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveUpAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveUpAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/MoveUpAction.java	(revision 9496)
@@ -0,0 +1,45 @@
+// 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.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Move the currently selected members up.
+ * @since 9496
+ */
+public class MoveUpAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code MoveUpAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param actionMapKey key in table action map
+     */
+    public MoveUpAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey) {
+        super(memberTable, memberTableModel, actionMapKey);
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
+        Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"), KeyEvent.VK_UP, Shortcut.ALT);
+        sc.setAccelerator(this);
+        putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tr("Move the currently selected members up"), sc));
+        setEnabled(false);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.moveUp(memberTable.getSelectedRows());
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/OKAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/OKAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/OKAction.java	(revision 9496)
@@ -0,0 +1,65 @@
+// 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.Component;
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Apply the updates and close the dialog.
+ */
+public class OKAction extends SavingAction {
+
+    /**
+     * Constructs a new {@code OKAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param tagModel tag editor model
+     * @param layer OSM data layer
+     * @param editor relation editor
+     * @param tfRole role text field
+     */
+    public OKAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer,
+            RelationAware editor, AutoCompletingTextField tfRole) {
+        super(memberTable, memberTableModel, tagModel, layer, editor, tfRole);
+        putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
+        putValue(SMALL_ICON, ImageProvider.get("ok"));
+        putValue(NAME, tr("OK"));
+        setEnabled(true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
+        memberTable.stopHighlighting();
+        if (editor.getRelation() == null) {
+            applyNewRelation(tagModel);
+        } else if (!memberTableModel.hasSameMembersAs(editor.getRelationSnapshot()) || tagModel.isDirty()) {
+            if (editor.isDirtyRelation()) {
+                if (confirmClosingBecauseOfDirtyState()) {
+                    if (layer.getConflicts().hasConflictForMy(editor.getRelation())) {
+                        warnDoubleConflict();
+                        return;
+                    }
+                    applyExistingConflictingRelation(tagModel);
+                } else
+                    return;
+            } else {
+                applyExistingNonConflictingRelation(tagModel);
+            }
+        }
+        if (editor instanceof Component) {
+            ((Component) editor).setVisible(false);
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java	(revision 9496)
@@ -0,0 +1,83 @@
+// 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.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * Paste members.
+ * @since 9496
+ */
+public class PasteMembersAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code PasteMembersAction}.
+     * @param memberTableModel member table model
+     * @param layer OSM data layer
+     * @param editor relation editor
+     */
+    public PasteMembersAction(MemberTableModel memberTableModel, OsmDataLayer layer, RelationAware editor) {
+        super(null, memberTableModel, null, null, null, layer, editor);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
+            DataSet ds = layer.data;
+            List<OsmPrimitive> toAdd = new ArrayList<>();
+            boolean hasNewInOtherLayer = false;
+
+            for (PrimitiveData primitive: primitives) {
+                OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
+                if (primitiveInDs != null) {
+                    toAdd.add(primitiveInDs);
+                } else if (!primitive.isNew()) {
+                    OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
+                    ds.addPrimitive(p);
+                    toAdd.add(p);
+                } else {
+                    hasNewInOtherLayer = true;
+                    break;
+                }
+            }
+
+            if (hasNewInOtherLayer) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("Members from paste buffer cannot be added because they are not included in current layer"));
+                return;
+            }
+
+            toAdd = filterConfirmedPrimitives(toAdd);
+            int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
+            if (index == -1) {
+                index = memberTableModel.getRowCount() - 1;
+            }
+            memberTableModel.addMembersAfterIdx(toAdd, index);
+
+        } catch (AddAbortException ex) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(ex.getMessage());
+            }
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        // Do nothing
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveAction.java	(revision 9496)
@@ -0,0 +1,46 @@
+// 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.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Remove the currently selected members from this relation.
+ * @since 9496
+ */
+public class RemoveAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code RemoveAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param actionMapKey action map key
+     */
+    public RemoveAction(MemberTable memberTable, MemberTableModel memberTableModel, String actionMapKey) {
+        super(memberTable, memberTableModel, actionMapKey);
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+        putValue(NAME, tr("Remove"));
+        Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"), KeyEvent.VK_DELETE, Shortcut.ALT);
+        sc.setAccelerator(this);
+        putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tr("Remove the currently selected members from this relation"), sc));
+        setEnabled(false);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.remove(memberTable.getSelectedRows());
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveSelectedAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveSelectedAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/RemoveSelectedAction.java	(revision 9496)
@@ -0,0 +1,48 @@
+// 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 org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Remove all members referring to one of the selected objects.
+ * @since 9496
+ */
+public class RemoveSelectedAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code RemoveSelectedAction}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param layer OSM data layer
+     */
+    public RemoveSelectedAction(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, OsmDataLayer layer) {
+        super(null, memberTableModel, null, selectionTableModel, null, layer, null);
+        putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        DataSet ds = layer.data;
+        if (ds == null || ds.getSelected().isEmpty()) {
+            setEnabled(false);
+            return;
+        }
+        // only enable the action if we have members referring to the selected primitives
+        setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java	(revision 9496)
@@ -0,0 +1,41 @@
+// 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 org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Reverse the order of the relation members.
+ * @since 9496
+ */
+public class ReverseAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code ReverseAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     */
+    public ReverseAction(MemberTable memberTable, MemberTableModel memberTableModel) {
+        super(memberTable, memberTableModel, null);
+        putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse"));
+        putValue(NAME, tr("Reverse"));
+    //  Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"), KeyEvent.VK_END, Shortcut.ALT)
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.reverse();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.getRowCount() > 0);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(revision 9496)
@@ -0,0 +1,168 @@
+// 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.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.conflict.ConflictAddCommand;
+import org.openstreetmap.josm.data.conflict.Conflict;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationAware;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Abstract superclass of relation saving actions (OK, Apply, Cancel).
+ * @since 9496
+ */
+abstract class SavingAction extends AbstractRelationEditorAction {
+
+    protected final TagEditorModel tagModel;
+    protected final AutoCompletingTextField tfRole;
+
+    protected SavingAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer,
+            RelationAware editor, AutoCompletingTextField tfRole) {
+        super(memberTable, memberTableModel, null, layer, editor);
+        this.tagModel = tagModel;
+        this.tfRole = tfRole;
+    }
+
+    /**
+     * apply updates to a new relation
+     * @param tagEditorModel tag editor model
+     */
+    protected void applyNewRelation(TagEditorModel tagEditorModel) {
+        final Relation newRelation = new Relation();
+        tagEditorModel.applyToPrimitive(newRelation);
+        memberTableModel.applyToRelation(newRelation);
+        List<RelationMember> newMembers = new ArrayList<>();
+        for (RelationMember rm: newRelation.getMembers()) {
+            if (!rm.getMember().isDeleted()) {
+                newMembers.add(rm);
+            }
+        }
+        if (newRelation.getMembersCount() != newMembers.size()) {
+            newRelation.setMembers(newMembers);
+            String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" +
+            "was open. They have been removed from the relation members list.");
+            JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+        // If the user wanted to create a new relation, but hasn't added any members or
+        // tags, don't add an empty relation
+        if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys())
+            return;
+        Main.main.undoRedo.add(new AddCommand(layer, newRelation));
+
+        // make sure everybody is notified about the changes
+        //
+        layer.data.fireSelectionChanged();
+        editor.setRelation(newRelation);
+        if (editor instanceof RelationEditor) {
+            RelationDialogManager.getRelationDialogManager().updateContext(
+                    layer, editor.getRelation(), (RelationEditor) editor);
+        }
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                // Relation list gets update in EDT so selecting my be postponed to following EDT run
+                Main.map.relationListDialog.selectRelation(newRelation);
+            }
+        });
+    }
+
+    /**
+     * Apply the updates for an existing relation which has been changed outside of the relation editor.
+     * @param tagEditorModel tag editor model
+     */
+    protected void applyExistingConflictingRelation(TagEditorModel tagEditorModel) {
+        Relation editedRelation = new Relation(editor.getRelation());
+        tagEditorModel.applyToPrimitive(editedRelation);
+        memberTableModel.applyToRelation(editedRelation);
+        Conflict<Relation> conflict = new Conflict<>(editor.getRelation(), editedRelation);
+        Main.main.undoRedo.add(new ConflictAddCommand(layer, conflict));
+    }
+
+    /**
+     * Apply the updates for an existing relation which has not been changed outside of the relation editor.
+     * @param tagEditorModel tag editor model
+     */
+    protected void applyExistingNonConflictingRelation(TagEditorModel tagEditorModel) {
+        Relation editedRelation = new Relation(editor.getRelation());
+        tagEditorModel.applyToPrimitive(editedRelation);
+        memberTableModel.applyToRelation(editedRelation);
+        Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation));
+        layer.data.fireSelectionChanged();
+        // this will refresh the snapshot and update the dialog title
+        //
+        editor.setRelation(editor.getRelation());
+    }
+
+    protected boolean confirmClosingBecauseOfDirtyState() {
+        ButtonSpec[] options = new ButtonSpec[] {
+                new ButtonSpec(
+                        tr("Yes, create a conflict and close"),
+                        ImageProvider.get("ok"),
+                        tr("Click to create a conflict and close this relation editor"),
+                        null /* no specific help topic */
+                ),
+                new ButtonSpec(
+                        tr("No, continue editing"),
+                        ImageProvider.get("cancel"),
+                        tr("Click to return to the relation editor and to resume relation editing"),
+                        null /* no specific help topic */
+                )
+        };
+
+        int ret = HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                tr("<html>This relation has been changed outside of the editor.<br>"
+                        + "You cannot apply your changes and continue editing.<br>"
+                        + "<br>"
+                        + "Do you want to create a conflict and close the editor?</html>"),
+                        tr("Conflict in data"),
+                        JOptionPane.WARNING_MESSAGE,
+                        null,
+                        options,
+                        options[0], // OK is default
+                        "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
+        );
+        return ret == 0;
+    }
+
+    protected void warnDoubleConflict() {
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("<html>Layer ''{0}'' already has a conflict for object<br>"
+                        + "''{1}''.<br>"
+                        + "Please resolve this conflict first, then try again.</html>",
+                        layer.getName(),
+                        editor.getRelation().getDisplayName(DefaultNameFormatter.getInstance())
+                ),
+                tr("Double conflict"),
+                JOptionPane.WARNING_MESSAGE
+        );
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        // Do nothing
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectPrimitivesForSelectedMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectPrimitivesForSelectedMembersAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectPrimitivesForSelectedMembersAction.java	(revision 9496)
@@ -0,0 +1,42 @@
+// 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 org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Selects primitives in the layer this editor belongs to. The selected primitives are
+ * equal to the set of primitives the currently selected relation members refer to.
+ * @since 9496
+ */
+public class SelectPrimitivesForSelectedMembersAction extends AbstractRelationEditorAction {
+
+    /**
+     * Select objects for selected relation members.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param layer layer
+     */
+    public SelectPrimitivesForSelectedMembersAction(MemberTable memberTable, MemberTableModel memberTableModel, OsmDataLayer layer) {
+        super(memberTable, memberTableModel, null, layer, null);
+        putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTable.getSelectedRowCount() > 0);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        layer.data.setSelected(memberTableModel.getSelectedChildPrimitives());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectedMembersForSelectionAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectedMembersForSelectionAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SelectedMembersForSelectionAction.java	(revision 9496)
@@ -0,0 +1,50 @@
+// 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 org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Selects  members in the relation editor which refer to primitives in the current selection of the context layer.
+ * @since 9496
+ */
+public class SelectedMembersForSelectionAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code SelectedMembersForSelectionAction}.
+     * @param memberTableModel member table model
+     * @param selectionTableModel selection table model
+     * @param layer OSM data layer
+     */
+    public SelectedMembersForSelectionAction(MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, OsmDataLayer layer) {
+        super(null, memberTableModel, null, selectionTableModel, null, layer, null);
+        putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
+        putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        boolean enabled = selectionTableModel.getRowCount() > 0
+        && !memberTableModel.getChildPrimitives(layer.data.getSelected()).isEmpty();
+
+        if (enabled) {
+            putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",
+                    memberTableModel.getChildPrimitives(layer.data.getSelected()).size()));
+        } else {
+            putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
+        }
+        setEnabled(enabled);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.selectMembersReferringTo(layer.data.getSelected());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java	(revision 9496)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.JOptionPane;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Sets a role for the selected members
+ * @since 9496
+ */
+public class SetRoleAction extends AbstractRelationEditorAction implements DocumentListener {
+
+    private final transient AutoCompletingTextField tfRole;
+
+    /**
+     * Constructs a new {@code SetRoleAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     * @param tfRole role text field
+     */
+    public SetRoleAction(MemberTable memberTable, MemberTableModel memberTableModel, AutoCompletingTextField tfRole) {
+        super(memberTable, memberTableModel, null);
+        this.tfRole = tfRole;
+        putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
+        putValue(SMALL_ICON, ImageProvider.get("apply"));
+        putValue(NAME, tr("Apply Role"));
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTable.getSelectedRowCount() > 0);
+    }
+
+    protected boolean isEmptyRole() {
+        return tfRole.getText() == null || tfRole.getText().trim().isEmpty();
+    }
+
+    protected boolean confirmSettingEmptyRole(int onNumMembers) {
+        String message = "<html>"
+            + trn("You are setting an empty role on {0} object.",
+                    "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers)
+                    + "<br>"
+                    + tr("This is equal to deleting the roles of these objects.") +
+                    "<br>"
+                    + tr("Do you really want to apply the new role?") + "</html>";
+        String[] options = new String[] {
+                tr("Yes, apply it"),
+                tr("No, do not apply")
+        };
+        int ret = ConditionalOptionPaneUtil.showOptionDialog(
+                "relation_editor.confirm_applying_empty_role",
+                Main.parent,
+                message,
+                tr("Confirm empty role"),
+                JOptionPane.YES_NO_OPTION,
+                JOptionPane.WARNING_MESSAGE,
+                options,
+                options[0]
+        );
+        switch(ret) {
+        case JOptionPane.YES_OPTION:
+        case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (isEmptyRole() && !confirmSettingEmptyRole(memberTable.getSelectedRowCount())) {
+            return;
+        }
+        memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
+    }
+
+    @Override
+    public void changedUpdate(DocumentEvent e) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void insertUpdate(DocumentEvent e) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void removeUpdate(DocumentEvent e) {
+        updateEnabledState();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortAction.java	(revision 9496)
@@ -0,0 +1,45 @@
+// 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.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Sort the relation members
+ * @since 9496
+ */
+public class SortAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code SortAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     */
+    public SortAction(MemberTable memberTable, MemberTableModel memberTableModel) {
+        super(memberTable, memberTableModel, null);
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
+        putValue(NAME, tr("Sort"));
+        Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"), KeyEvent.VK_END, Shortcut.ALT);
+        sc.setAccelerator(this);
+        putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tr("Sort the relation members"), sc));
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.sort();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.getRowCount() > 0);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortBelowAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortBelowAction.java	(revision 9496)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SortBelowAction.java	(revision 9496)
@@ -0,0 +1,40 @@
+// 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 org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Sort the selected relation members and all members below.
+ * @since 9496
+ */
+public class SortBelowAction extends AbstractRelationEditorAction {
+
+    /**
+     * Constructs a new {@code SortBelowAction}.
+     * @param memberTable member table
+     * @param memberTableModel member table model
+     */
+    public SortBelowAction(MemberTable memberTable, MemberTableModel memberTableModel) {
+        super(memberTable, memberTableModel, null);
+        putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort_below"));
+        putValue(NAME, tr("Sort below"));
+        putValue(SHORT_DESCRIPTION, tr("Sort the selected relation members and all members below"));
+        updateEnabledState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        memberTableModel.sortBelow();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(memberTableModel.getRowCount() > 0 && !memberTableModel.getSelectionModel().isSelectionEmpty());
+    }
+}
