Index: resources/images/in_dataset.svg
===================================================================
--- resources/images/in_dataset.svg	(nonexistent)
+++ resources/images/in_dataset.svg	(working copy)
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   width="16px"
+   height="16px"
+   id="svg79288"
+   sodipodi:docname="in_dataset.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs79292" />
+  <sodipodi:namedview
+     id="namedview79290"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="true"
+     inkscape:zoom="89.875"
+     inkscape:cx="3.3769124"
+     inkscape:cy="8.0055633"
+     inkscape:window-width="3840"
+     inkscape:window-height="2085"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg79288">
+    <inkscape:grid
+       type="xygrid"
+       id="grid79357"
+       empspacing="8"
+       dotted="false"
+       originx="8"
+       originy="8"
+       spacingx="0.5"
+       spacingy="0.5" />
+  </sodipodi:namedview>
+  <path
+     style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 3,3 13,6 5,13 Z"
+     id="path80027"
+     sodipodi:nodetypes="cccc" />
+  <path
+     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 11.5,4.5 h 3 v 3 h -3 z"
+     id="path79392" />
+  <path
+     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 1.5,1.5 h 3 v 3 h -3 z"
+     id="path79392-7" />
+  <path
+     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 3.5,11.5 h 3 v 3 h -3 z"
+     id="path79392-4" />
+</svg>
Index: resources/images/in_standard.svg
===================================================================
--- resources/images/in_standard.svg	(nonexistent)
+++ resources/images/in_standard.svg	(working copy)
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="in_standard.svg"
+   id="svg12"
+   version="1.1"
+   width="16"
+   viewBox="0 0 16 16"
+   height="16"
+   enable-background="new 0 0 96 96"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata18">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs16" />
+  <sodipodi:namedview
+     inkscape:current-layer="svg12"
+     inkscape:window-maximized="1"
+     inkscape:window-y="0"
+     inkscape:window-x="0"
+     inkscape:cy="9.5865506"
+     inkscape:cx="7.8530512"
+     inkscape:zoom="66.916666"
+     showgrid="true"
+     id="namedview14"
+     inkscape:window-height="2085"
+     inkscape:window-width="3840"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     inkscape:pagecheckerboard="0"
+     inkscape:lockguides="false">
+    <inkscape:grid
+       id="grid843"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       empspacing="4" />
+  </sodipodi:namedview>
+  <text
+     xml:space="preserve"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     x="11.570769"
+     y="11.741786"
+     id="text12471"><tspan
+       sodipodi:role="line"
+       id="tspan12469"
+       x="11.570769"
+       y="11.741786"></tspan></text>
+  <text
+     xml:space="preserve"
+     style="font-size:13.3333px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#df421e;fill-opacity:1;stroke:#df421e;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="8.0417976"
+     y="11.405445"
+     id="text59954"><tspan
+       sodipodi:role="line"
+       id="tspan59952"
+       x="8.0417976"
+       y="11.405445">§</tspan></text>
+</svg>
Index: src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UploadAction.java	(revision 18287)
+++ src/org/openstreetmap/josm/actions/UploadAction.java	(working copy)
@@ -240,8 +240,7 @@
         ChangesetUpdater.check();
 
         final UploadDialog dialog = UploadDialog.getUploadDialog();
-        dialog.setUploadedPrimitives(apiData);
-        dialog.initLifeCycle(layer.getDataSet());
+        dialog.initLifeCycle(layer.getDataSet(), apiData);
         dialog.setVisible(true);
         dialog.rememberUserInput();
         if (dialog.isCanceled()) {
Index: src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java
===================================================================
--- src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java	(nonexistent)
+++ src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java	(working copy)
@@ -0,0 +1,79 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.tagging.ac;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+
+import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
+
+/**
+ * A custom list cell renderer for autocompletion items that colorizes and adds the value count to
+ * some items.
+ * <p>
+ * See also: {@link AutoCompletionPriority#compareTo}
+ */
+public class AutoCompItemCellRenderer extends JosmListCellRenderer<AutoCompletionItem> {
+    /** The color used to render items found in the dataset. */
+    public static final Color BGCOLOR_1 = new Color(254, 226, 214);
+    /** The color used to render items found in the standard */
+    public static final Color BGCOLOR_2 = new Color(235, 255, 177);
+
+    protected Map<String, Integer> map;
+    private static final ImageIcon iconEmpty = ImageProvider.getEmpty(ImageSizes.POPUPMENU);
+    private static final ImageIcon iconDataSet = ImageProvider.get("in_dataset", ImageSizes.POPUPMENU);
+    private static final ImageIcon iconStandard = ImageProvider.get("in_standard", ImageSizes.POPUPMENU);
+
+    /**
+     * Constructs the cell renderer.
+     *
+     * @param component The component the renderer is attached to. JComboBox or JList.
+     * @param renderer The L&amp;F renderer. Usually obtained by calling {@code getRenderer()} on {@code component}.
+     * @param map A map from key to count.
+     */
+    public AutoCompItemCellRenderer(Component component, ListCellRenderer<? super AutoCompletionItem> renderer, Map<String, Integer> map) {
+        super(component, renderer);
+        this.map = map;
+    }
+
+    @Override
+    public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list, AutoCompletionItem value,
+                                                int index, boolean isSelected, boolean cellHasFocus) {
+        Integer count = null;
+        if (value == null)
+            value = new AutoCompletionItem("", AutoCompletionPriority.IS_IN_STANDARD);
+
+        // if there is a value count add it to the text
+        if (map != null) {
+            String text = value.toString();
+            count = map.get(text);
+            if (count != null) {
+                value = new AutoCompletionItem(tr("{0} ({1})", text, count), value.getPriority());
+            }
+        }
+
+        JLabel l = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+        l.setIcon(iconEmpty);
+        if (value.getPriority().isInDataSet()) {
+            l.setIcon(iconDataSet);
+        }
+        if (value.getPriority().isInStandard()) {
+            l.setIcon(iconStandard);
+        }
+        l.setComponentOrientation(component.getComponentOrientation());
+        if (count != null) {
+            l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD));
+        }
+        return l;
+    }
+}
Index: src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(working copy)
@@ -10,12 +10,11 @@
 import java.awt.FlowLayout;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
+import java.awt.Insets;
 import java.awt.Window;
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.FlavorListener;
 import java.awt.event.ActionEvent;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
@@ -27,7 +26,9 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -35,6 +36,7 @@
 import javax.swing.BorderFactory;
 import javax.swing.InputMap;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
@@ -45,6 +47,7 @@
 import javax.swing.JSplitPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
+import javax.swing.JTextField;
 import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
 
@@ -58,6 +61,9 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompItemCellRenderer;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
 import org.openstreetmap.josm.data.validation.tests.RelationChecker;
 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -98,9 +104,11 @@
 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;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.tagging.TagTable;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
@@ -108,6 +116,7 @@
 import org.openstreetmap.josm.gui.util.WindowGeometry;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.InputMapUtils;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -118,6 +127,9 @@
  * @since 343
  */
 public class GenericRelationEditor extends RelationEditor implements CommandQueueListener {
+    private static final String PREF_LASTROLE = "relation.editor.generic.lastrole";
+    private static final String PREF_USE_ROLE_FILTER = "relation.editor.use_role_filter";
+
     /** the tag table and its model */
     private final TagEditorPanel tagEditorPanel;
     private final ReferringRelationsBrowser referrerBrowser;
@@ -131,7 +143,8 @@
     private final SelectionTable selectionTable;
     private final SelectionTableModel selectionTableModel;
 
-    private final AutoCompletingTextField tfRole;
+    private final AutoCompletionManager manager;
+    private final AutoCompComboBox<AutoCompletionItem> cbRole;
 
     /**
      * the menu item in the windows menu. Required to properly hide on dialog close.
@@ -172,6 +185,7 @@
 
     private Component selectedTabPane;
     private JTabbedPane tabbedPane;
+    private JCheckBox btnFilter = new JCheckBox(tr("Filter"));
 
     /**
      * Creates a new relation editor for the given relation. The relation will be saved if the user
@@ -212,25 +226,69 @@
         selectionTableModel.register();
         referrerModel = new ReferringRelationsBrowserModel(relation);
 
+        manager = AutoCompletionManager.of(this.getLayer().data);
+
         tagEditorPanel = new TagEditorPanel(relation, presetHandler);
+        TagTable tagTable = tagEditorPanel.getTable();
         populateModels(relation);
         tagEditorPanel.getModel().ensureOneTag();
 
+        // setting up the tag table
+        AutoCompComboBox<AutoCompletionItem> keyEditor = new AutoCompComboBox<>();
+        AutoCompComboBox<AutoCompletionItem> valueEditor = new AutoCompComboBox<>();
+        KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager();
+        ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager();
+        keyEditor.getEditorComponent().setMaxTextLength(256);
+        keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+        keyEditor.getEditorComponent().enableUndoRedo(false);
+        keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager);
+        keyEditor.addPopupMenuListener(keyAutoCompManager);
+        keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
+        keyEditor.setRenderer(new AutoCompItemCellRenderer(keyEditor, keyEditor.getRenderer(), null));
+
+        valueEditor.getEditorComponent().setMaxTextLength(-1);
+        valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+        valueEditor.getEditorComponent().enableUndoRedo(false);
+        valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager);
+        valueEditor.addPopupMenuListener(valueAutoCompManager);
+        valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
+        valueEditor.setRenderer(new AutoCompItemCellRenderer(valueEditor, valueEditor.getRenderer(), null));
+
+        tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height);
+        tagTable.setKeyEditor(keyEditor);
+        tagTable.setValueEditor(valueEditor);
+
         // setting up the member table
-        memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
+        AutoCompComboBox<AutoCompletionItem> cbRoleEditor = new AutoCompComboBox<>();
+        RoleAutoCompManager roleAutoCompManager = new RoleAutoCompManager();
+        cbRoleEditor.getEditorComponent().addAutoCompListener(roleAutoCompManager);
+        cbRoleEditor.addPopupMenuListener(roleAutoCompManager);
+        cbRoleEditor.getEditorComponent().enableUndoRedo(false);
+        Insets insets = cbRoleEditor.getEditorComponent().getInsets();
+        cbRoleEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(0, insets.left, 0, insets.right));
+        cbRoleEditor.setToolTipText(tr("Select a role for this relation member"));
+        cbRoleEditor.setRenderer(new AutoCompItemCellRenderer(cbRoleEditor, cbRoleEditor.getRenderer(), null));
+
+        int height = cbRoleEditor.getEditorComponent().getPreferredSize().height;
+        memberTable = new MemberTable(getLayer(), cbRoleEditor, memberTableModel);
         memberTable.addMouseListener(new MemberTableDblClickAdapter());
+        memberTable.setRowHeight(height);
         memberTableModel.addMemberModelListener(memberTable);
 
-        MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
         selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
-        selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
+        selectionTable.setRowHeight(height);
 
         LeftButtonToolbar leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess());
-        tfRole = buildRoleTextField(this);
+        cbRole = new AutoCompComboBox<>();
+        cbRole.getEditorComponent().addAutoCompListener(roleAutoCompManager);
+        cbRole.addPopupMenuListener(roleAutoCompManager);
+        cbRole.setText(Config.getPref().get(PREF_LASTROLE, ""));
+        cbRole.setToolTipText(tr("Select a role"));
+        cbRole.setRenderer(new AutoCompItemCellRenderer(cbRole, cbRole.getRenderer(), null));
 
         JSplitPane pane = buildSplitPane(
                 buildTagEditorPanel(tagEditorPanel),
-                buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
+                buildMemberEditorPanel(leftButtonToolbar),
                 this);
         pane.setPreferredSize(new Dimension(100, 100));
 
@@ -310,7 +368,7 @@
                 @Override
                 public void actionPerformed(ActionEvent e) {
                     super.actionPerformed(e);
-                    tfRole.requestFocusInWindow();
+                    cbRole.requestFocusInWindow();
                 }
             }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
         }
@@ -446,47 +504,14 @@
     }
 
     /**
-     * builds the role text field
-     * @param re relation editor
-     * @return the role text field
-     */
-    protected static AutoCompletingTextField buildRoleTextField(final IRelationEditor re) {
-        final AutoCompletingTextField tfRole = new AutoCompletingTextField(10);
-        tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
-        tfRole.addFocusListener(new FocusAdapter() {
-            @Override
-            public void focusGained(FocusEvent e) {
-                tfRole.selectAll();
-            }
-        });
-        tfRole.setAutoCompletionList(new AutoCompletionList());
-        tfRole.addFocusListener(
-                new FocusAdapter() {
-                    @Override
-                    public void focusGained(FocusEvent e) {
-                        AutoCompletionList list = tfRole.getAutoCompletionList();
-                        if (list != null) {
-                            list.clear();
-                            AutoCompletionManager.of(re.getLayer().data).populateWithMemberRoles(list, re.getRelation());
-                        }
-                    }
-                }
-        );
-        tfRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));
-        return tfRole;
-    }
-
-    /**
      * builds the panel for the relation member editor
      * @param leftButtonToolbar left button toolbar
-     * @param editorAccess The relation editor
      *
      * @return the panel for the relation member editor
      */
-    protected static JPanel buildMemberEditorPanel(
-            LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) {
+    protected JPanel buildMemberEditorPanel(LeftButtonToolbar leftButtonToolbar) {
         final JPanel pnl = new JPanel(new GridBagLayout());
-        final JScrollPane scrollPane = new JScrollPane(editorAccess.getMemberTable());
+        final JScrollPane scrollPane = new JScrollPane(memberTable);
 
         GridBagConstraints gc = new GridBagConstraints();
         gc.gridx = 0;
@@ -518,22 +543,34 @@
         pnl.add(scrollPane, gc);
 
         // --- role editing
-        JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
-        p3.add(new JLabel(tr("Apply Role:")));
-        p3.add(editorAccess.getTextFieldRole());
-        SetRoleAction setRoleAction = new SetRoleAction(editorAccess);
-        editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(setRoleAction);
-        editorAccess.getTextFieldRole().getDocument().addDocumentListener(setRoleAction);
-        editorAccess.getTextFieldRole().addActionListener(setRoleAction);
-        editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(
-                e -> editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0)
+        JPanel p3 = new JPanel(new GridBagLayout());
+        GBC gbc = GBC.std().fill(GridBagConstraints.NONE);
+        JLabel lbl = new JLabel(tr("Role:"));
+        p3.add(lbl, gbc);
+
+        p3.add(cbRole, gbc.insets(3, 3, 0, 3).fill(GridBagConstraints.HORIZONTAL));
+
+        SetRoleAction setRoleAction = new SetRoleAction(new RelationEditorActionAccess());
+        memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
+        cbRole.getEditorComponent().getDocument().addDocumentListener(setRoleAction);
+        cbRole.getEditorComponent().addActionListener(setRoleAction);
+        memberTableModel.getSelectionModel().addListSelectionListener(
+                e -> cbRole.setEnabled(memberTable.getSelectedRowCount() > 0)
         );
-        editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0);
+        cbRole.setEnabled(memberTable.getSelectedRowCount() > 0);
+
         JButton btnApply = new JButton(setRoleAction);
-        btnApply.setPreferredSize(new Dimension(20, 20));
+        int height = cbRole.getPreferredSize().height;
+        btnApply.setPreferredSize(new Dimension(height, height));
         btnApply.setText("");
-        p3.add(btnApply);
+        p3.add(btnApply, gbc.weight(0, 0).fill(GridBagConstraints.NONE));
 
+        btnFilter.setToolTipText(tr("Filter suggestions based on context"));
+        btnFilter.setSelected(Config.getPref().getBoolean(PREF_USE_ROLE_FILTER, false));
+        p3.add(btnFilter, gbc.span(GridBagConstraints.REMAINDER));
+
+        //
+
         gc.gridx = 1;
         gc.gridy = 2;
         gc.fill = GridBagConstraints.HORIZONTAL;
@@ -562,7 +599,7 @@
         gc.anchor = GridBagConstraints.NORTHWEST;
         gc.weightx = 0.0;
         gc.weighty = 1.0;
-        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(editorAccess),
+        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(new RelationEditorActionAccess()),
                 ScrollViewport.VERTICAL_DIRECTION), gc);
 
         gc.gridx = 1;
@@ -570,21 +607,19 @@
         gc.weightx = 1.0;
         gc.weighty = 1.0;
         gc.fill = GridBagConstraints.BOTH;
-        pnl2.add(buildSelectionTablePanel(editorAccess.getSelectionTable()), gc);
+        pnl2.add(buildSelectionTablePanel(selectionTable), gc);
 
         final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
         splitPane.setLeftComponent(pnl);
         splitPane.setRightComponent(pnl2);
         splitPane.setOneTouchExpandable(false);
-        if (editorAccess.getEditor() instanceof Window) {
-            ((Window) editorAccess.getEditor()).addWindowListener(new WindowAdapter() {
-                @Override
-                public void windowOpened(WindowEvent e) {
-                    // has to be called when the window is visible, otherwise no effect
-                    splitPane.setDividerLocation(0.6);
-                }
-            });
-        }
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowOpened(WindowEvent e) {
+                // has to be called when the window is visible, otherwise no effect
+                splitPane.setDividerLocation(0.6);
+            }
+        });
 
         JPanel pnl3 = new JPanel(new BorderLayout());
         pnl3.add(splitPane, BorderLayout.CENTER);
@@ -739,9 +774,6 @@
         if (isVisible() == visible) {
             return;
         }
-        if (visible) {
-            tagEditorPanel.initAutoCompletion(getLayer());
-        }
         super.setVisible(visible);
         Clipboard clipboard = ClipboardUtils.getClipboard();
         if (visible) {
@@ -754,6 +786,9 @@
                 clipboard.addFlavorListener(listener);
             }
         } else {
+            Config.getPref().put(PREF_LASTROLE, cbRole.getText());
+            Config.getPref().putBoolean(PREF_USE_ROLE_FILTER, btnFilter.isSelected());
+
             // make sure all registered listeners are unregistered
             //
             memberTable.stopHighlighting();
@@ -1039,10 +1074,9 @@
         }
 
         @Override
-        public AutoCompletingTextField getTextFieldRole() {
-            return tfRole;
+        public JTextField getTextFieldRole() {
+            return cbRole.getEditorComponent();
         }
-
     }
 
     @Override
@@ -1054,4 +1088,80 @@
             applyAction.updateEnabledState();
         }
     }
+
+    private class KeyAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
+        @Override
+        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
+            Map<String, AutoCompletionPriority> map;
+            Map<String, String> keys = tagEditorPanel.getModel().getTags();
+            Map<String, String> matchKeys = btnFilter.isSelected() ? keys : null;
+
+            map = AutoCompletionManager.merge(
+                manager.getKeysForRelation(matchKeys),
+                manager.getPresetKeys(EnumSet.of(TaggingPresetType.RELATION), matchKeys)
+            );
+
+            model.replaceAllElements(map.entrySet().stream().filter(e -> !keys.containsKey(e.getKey()))
+                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
+                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
+        }
+    }
+
+    private class ValueAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
+        @Override
+        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
+            Map<String, AutoCompletionPriority> map;
+            Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null;
+            String key = (String) tagEditorPanel.getModel().getValueAt(tagEditorPanel.getTable().getEditingRow(), 0);
+
+            map = AutoCompletionManager.merge(
+                manager.getValuesForRelation(keys, key),
+                manager.getPresetValues(EnumSet.of(TaggingPresetType.RELATION), keys, key)
+            );
+
+            model.replaceAllElements(map.entrySet().stream()
+                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
+                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
+        }
+    }
+
+    /**
+     * Returns the roles currently edited in the members table.
+     * @param types the preset types to include, (node / way / relation ...) or null to include all types
+     * @return the roles currently edited in the members table.
+     */
+    private Map<String, AutoCompletionPriority> getCurrentRoles(Collection<TaggingPresetType> types) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+        for (int i = 0; i < memberTableModel.getRowCount(); ++i) {
+            RelationMember member = memberTableModel.getValue(i);
+            if (types == null || types.contains(TaggingPresetType.forPrimitiveType(member.getDisplayType()))) {
+                map.merge(member.getRole(), AutoCompletionPriority.IS_IN_SELECTION, AutoCompletionPriority::mergeWith);
+            }
+        }
+        return map;
+    }
+
+    private class RoleAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
+        @Override
+        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
+            Map<String, AutoCompletionPriority> map;
+            Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null;
+
+            EnumSet<TaggingPresetType> selectedTypes = EnumSet.noneOf(TaggingPresetType.class);
+            for (RelationMember member : memberTableModel.getSelectedMembers()) {
+                selectedTypes.add(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
+            }
+
+            map = AutoCompletionManager.merge(
+                manager.getRolesForRelation(keys, selectedTypes),
+                manager.getPresetRoles(keys, selectedTypes),
+                getCurrentRoles(selectedTypes)
+            );
+
+            // turn into AutoCompletionItems
+            model.replaceAllElements(map.entrySet().stream()
+                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
+                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
+        }
+    }
 }
Index: src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java	(nonexistent)
@@ -1,66 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-import java.awt.Component;
-
-import javax.swing.AbstractCellEditor;
-import javax.swing.BorderFactory;
-import javax.swing.CellEditor;
-import javax.swing.JTable;
-import javax.swing.table.TableCellEditor;
-
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
-
-/**
- * The {@link CellEditor} for the role cell in the table. Supports autocompletion.
- */
-public class MemberRoleCellEditor extends AbstractCellEditor implements TableCellEditor {
-    private final AutoCompletingTextField editor;
-    private final AutoCompletionManager autoCompletionManager;
-    private final transient Relation relation;
-
-    /** user input is matched against this list of auto completion items */
-    private final AutoCompletionList autoCompletionList;
-
-    /**
-     * Constructs a new {@code MemberRoleCellEditor}.
-     * @param autoCompletionManager the auto completion manager. Must not be null
-     * @param relation the relation. Can be null
-     * @since 13675
-     */
-    public MemberRoleCellEditor(AutoCompletionManager autoCompletionManager, Relation relation) {
-        this.autoCompletionManager = autoCompletionManager;
-        this.relation = relation;
-        editor = new AutoCompletingTextField(0, false);
-        editor.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
-        autoCompletionList = new AutoCompletionList();
-        editor.setAutoCompletionList(autoCompletionList);
-    }
-
-    @Override
-    public Component getTableCellEditorComponent(JTable table,
-            Object value, boolean isSelected, int row, int column) {
-
-        String role = (String) value;
-        editor.setText(role);
-        autoCompletionList.clear();
-        autoCompletionManager.populateWithMemberRoles(autoCompletionList, relation);
-        return editor;
-    }
-
-    @Override
-    public Object getCellEditorValue() {
-        return editor.getText();
-    }
-
-    /**
-     * Returns the edit field for this cell editor.
-     * @return the edit field for this cell editor
-     */
-    public AutoCompletingTextField getEditor() {
-        return editor;
-    }
-}

Property changes on: src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java	(working copy)
@@ -20,6 +20,7 @@
 import javax.swing.SwingUtilities;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
 
 import org.openstreetmap.josm.actions.AbstractShowHistoryAction;
 import org.openstreetmap.josm.actions.AutoScaleAction;
@@ -27,7 +28,6 @@
 import org.openstreetmap.josm.actions.HistoryInfoAction;
 import org.openstreetmap.josm.actions.ZoomToAction;
 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.data.osm.Way;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -41,7 +41,6 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -60,16 +59,15 @@
      * constructor for relation member table
      *
      * @param layer the data layer of the relation. Must not be null
-     * @param relation the relation. Can be null
+     * @param roleCellEditor the role editor combobox
      * @param model the table model
      */
-    public MemberTable(OsmDataLayer layer, Relation relation, MemberTableModel model) {
-        super(model, new MemberTableColumnModel(AutoCompletionManager.of(layer.data), relation), model.getSelectionModel());
+    public MemberTable(OsmDataLayer layer, TableCellEditor roleCellEditor, MemberTableModel model) {
+        super(model, new MemberTableColumnModel(roleCellEditor), model.getSelectionModel());
         setLayer(layer);
         model.addMemberModelListener(this);
 
-        MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor();
-        setRowHeight(ce.getEditor().getPreferredSize().height);
+        setRowHeight(roleCellEditor.getTableCellEditorComponent(this, "", false, 0, 0).getPreferredSize().height);
         setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
         setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
         putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
@@ -83,9 +81,7 @@
         if (!GraphicsEnvironment.isHeadless()) {
             setTransferHandler(new MemberTransferHandler());
             setFillsViewportHeight(true); // allow drop on empty table
-            if (!GraphicsEnvironment.isHeadless()) {
-                setDragEnabled(true);
-            }
+            setDragEnabled(true);
             setDropMode(DropMode.INSERT_ROWS);
         }
     }
Index: src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java	(working copy)
@@ -4,11 +4,9 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableColumn;
 
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
-
 /**
  * This is the column model for the {@link MemberTable}
  */
@@ -16,11 +14,10 @@
 
     /**
      * Constructs a new {@code MemberTableColumnModel}.
-     * @param autoCompletionManager the auto completion manager. Must not be null
-     * @param relation the relation. Can be null
+     * @param roleCellEditor the role editor combobox
      * @since 13675
      */
-    public MemberTableColumnModel(AutoCompletionManager autoCompletionManager, Relation relation) {
+    public MemberTableColumnModel(TableCellEditor roleCellEditor) {
         TableColumn col;
 
         // column 0 - the member role
@@ -29,7 +26,7 @@
         col.setResizable(true);
         col.setPreferredWidth(100);
         col.setCellRenderer(new MemberTableRoleCellRenderer());
-        col.setCellEditor(new MemberRoleCellEditor(autoCompletionManager, relation));
+        col.setCellEditor(roleCellEditor);
         addColumn(col);
 
         // column 1 - the member
Index: src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java	(working copy)
@@ -2,6 +2,7 @@
 package org.openstreetmap.josm.gui.dialogs.relation.actions;
 
 import javax.swing.Action;
+import javax.swing.JTextField;
 
 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
 import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
@@ -9,7 +10,6 @@
 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTable;
 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
 import org.openstreetmap.josm.gui.tagging.TagEditorModel;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
 
 /**
  * This interface provides access to the relation editor for actions.
@@ -69,7 +69,7 @@
      * Get the text field that is used to edit the role.
      * @return The role text field.
      */
-    AutoCompletingTextField getTextFieldRole();
+    JTextField getTextFieldRole();
 
     /**
      * Tells the member table editor to stop editing and accept any partially edited value as the value of the editor.
Index: src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(working copy)
@@ -8,6 +8,7 @@
 import java.util.List;
 
 import javax.swing.JOptionPane;
+import javax.swing.JTextField;
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.command.AddCommand;
@@ -27,7 +28,6 @@
 import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
 import org.openstreetmap.josm.gui.tagging.TagEditorModel;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -38,7 +38,7 @@
 abstract class SavingAction extends AbstractRelationEditorAction {
     private static final long serialVersionUID = 1L;
 
-    protected final AutoCompletingTextField tfRole;
+    protected final JTextField tfRole;
 
     protected SavingAction(IRelationEditorActionAccess editorAccess, IRelationEditorUpdateOn... updateOn) {
         super(editorAccess, updateOn);
Index: src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java	(working copy)
@@ -7,12 +7,12 @@
 import java.awt.event.ActionEvent;
 
 import javax.swing.JOptionPane;
+import javax.swing.JTextField;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 
 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -23,7 +23,7 @@
 public class SetRoleAction extends AbstractRelationEditorAction implements DocumentListener {
     private static final long serialVersionUID = 1L;
 
-    private final transient AutoCompletingTextField tfRole;
+    private final transient JTextField tfRole;
 
     /**
      * Constructs a new {@code SetRoleAction}.
@@ -32,7 +32,7 @@
     public SetRoleAction(IRelationEditorActionAccess editorAccess) {
         super(editorAccess);
         this.tfRole = editorAccess.getTextFieldRole();
-        putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
+        putValue(SHORT_DESCRIPTION, tr("Apply the role to the selected members"));
         new ImageProvider("apply").getResource().attachImageIcon(this);
         putValue(NAME, tr("Apply Role"));
         updateEnabledState();
Index: src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/io/UploadDialog.java	(working copy)
@@ -18,7 +18,7 @@
 import java.beans.PropertyChangeListener;
 import java.lang.Character.UnicodeBlock;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -43,6 +43,10 @@
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
+import org.openstreetmap.josm.gui.tagging.TagTable;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
+import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.MultiLineFlowLayout;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
@@ -88,8 +92,6 @@
     /** the model keeping the state of the changeset tags */
     private final transient UploadDialogModel model = new UploadDialogModel();
 
-    private transient DataSet dataSet;
-
     /**
      * Constructs a new {@code UploadDialog}.
      */
@@ -141,6 +143,30 @@
         pnlTagEditor = new TagEditorPanel(model, null, Changeset.MAX_CHANGESET_TAG_LENGTH);
         pnlTagEditorBorder.add(pnlTagEditor, BorderLayout.CENTER);
 
+        // setting up the tag table
+        TagTable tagTable = pnlTagEditor.getTable();
+        AutoCompComboBox<String> keyEditor = new AutoCompComboBox<>();
+        AutoCompComboBox<String> valueEditor = new AutoCompComboBox<>();
+        KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager();
+        ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager();
+        keyEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
+        keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+        keyEditor.getEditorComponent().enableUndoRedo(false);
+        keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager);
+        keyEditor.addPopupMenuListener(keyAutoCompManager);
+        keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
+
+        valueEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
+        valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+        valueEditor.getEditorComponent().enableUndoRedo(false);
+        valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager);
+        valueEditor.addPopupMenuListener(valueAutoCompManager);
+        valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
+
+        tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height);
+        tagTable.setKeyEditor(keyEditor);
+        tagTable.setValueEditor(valueEditor);
+
         pnlChangesetManagement = new ChangesetManagementPanel();
         pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
         pnlSettings.add(pnlChangesetManagement, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
@@ -228,19 +254,18 @@
      * this in the constructor because the dialog is a singleton.
      *
      * @param dataSet The Dataset we want to upload
+     * @param toUpload The primitves to upload
      * @since 18173
      */
-    public void initLifeCycle(DataSet dataSet) {
+    public void initLifeCycle(DataSet dataSet, APIDataSet toUpload) {
         Map<String, String> map = new HashMap<>();
-        this.dataSet = dataSet;
         pnlBasicUploadSettings.initLifeCycle(map);
         pnlChangesetManagement.initLifeCycle();
         model.clear();
-        model.putAll(map);          // init with tags from history
-        model.putAll(this.dataSet); // overwrite with tags from the dataset
+        model.putAll(map);     // init with tags from history
+        model.putAll(dataSet); // overwrite with tags from the dataset
 
         tpConfigPanels.setSelectedIndex(0);
-        pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
         pnlUploadStrategySelectionPanel.initFromPreferences();
 
         // update the summary
@@ -247,32 +272,11 @@
         UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
         sumPnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
         sumPnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
-    }
 
-    /**
-     * Sets the collection of primitives to upload
-     *
-     * @param toUpload the dataset with the objects to upload. If null, assumes the empty
-     * set of objects to upload
-     *
-     */
-    public void setUploadedPrimitives(APIDataSet toUpload) {
-        UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
-        if (toUpload == null) {
-            if (pnlUploadedObjects != null) {
-                List<OsmPrimitive> emptyList = Collections.emptyList();
-                pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
-                sumPnl.setNumObjects(0);
-            }
-            return;
-        }
         List<OsmPrimitive> l = toUpload.getPrimitives();
         pnlBasicUploadSettings.setUploadedPrimitives(l);
-        pnlUploadedObjects.setUploadedPrimitives(
-                toUpload.getPrimitivesToAdd(),
-                toUpload.getPrimitivesToUpdate(),
-                toUpload.getPrimitivesToDelete()
-        );
+        pnlUploadedObjects.removeAll();
+        pnlUploadedObjects.build(toUpload);
         sumPnl.setNumObjects(l.size());
         pnlUploadStrategySelectionPanel.setNumUploadedObjects(l.size());
     }
@@ -512,6 +516,34 @@
         }
     }
 
+    private static class KeyAutoCompManager extends DefaultAutoCompListener<String> {
+        @Override
+        protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) {
+            model.replaceAllElements(Arrays.asList("comment", "source", "review_requested", "created_by", "imagery_used", "locale"));
+            // FIXME add more tags from user upload history?
+        }
+    }
+
+    private class ValueAutoCompManager extends DefaultAutoCompListener<String> {
+        @Override
+        protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) {
+            String key = (String) pnlTagEditor.getModel().getValueAt(pnlTagEditor.getTable().getEditingRow(), 0);
+            if ("comment".equals(key)) {
+                model.prefs(x ->x, x -> x).load(BasicUploadSettingsPanel.COMMENT_HISTORY_KEY);
+                return;
+            }
+            if ("source".equals(key)) {
+                model.prefs(x -> x, x -> x).load(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
+                return;
+            }
+            if ("review_requested".equals(key)) {
+                model.replaceAllElements(Arrays.asList("yes", ""));
+                return;
+            }
+            model.replaceAllElements(Arrays.asList(""));
+        }
+    }
+
     /* -------------------------------------------------------------------------- */
     /* Interface PropertyChangeListener                                           */
     /* -------------------------------------------------------------------------- */
@@ -604,7 +636,6 @@
      * @since 14251
      */
     public void clean() {
-        setUploadedPrimitives(null);
-        dataSet = null;
+        pnlUploadedObjects.removeAll();
     }
 }
Index: src/org/openstreetmap/josm/gui/io/UploadDialogModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/UploadDialogModel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/io/UploadDialogModel.java	(working copy)
@@ -75,12 +75,12 @@
      * @return the hashtags separated by ";" or null
      */
     String findHashTags(String comment) {
-        String hashtags = String.join(";",
+        String hashTags = String.join(";",
             Arrays.stream(comment.split("\\s", -1))
                 .map(s -> Utils.strip(s, ",;"))
                 .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+"))
                 .collect(Collectors.toList()));
-        return hashtags.isEmpty() ? null : hashtags;
+        return hashTags.isEmpty() ? null : hashTags;
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(working copy)
@@ -401,7 +401,7 @@
                         // return to the upload dialog
                         //
                         toUpload.removeProcessed(processedPrimitives);
-                        UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
+                        UploadDialog.getUploadDialog().initLifeCycle(null, toUpload);
                         UploadDialog.getUploadDialog().setVisible(true);
                         break;
                     }
Index: src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java	(working copy)
@@ -69,7 +69,7 @@
 
     protected JPanel buildUploadStrategyPanel() {
         JPanel pnl = new JPanel(new GridBagLayout());
-        pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select the upload strategy:")));
+        pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select an upload strategy:")));
         ButtonGroup bgStrategies = new ButtonGroup();
         rbStrategy = new EnumMap<>(UploadStrategy.class);
         lblNumRequests = new EnumMap<>(UploadStrategy.class);
Index: src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java	(working copy)
@@ -1,7 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.io;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.GridBagConstraints;
@@ -8,12 +7,11 @@
 import java.awt.GridBagLayout;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 
-import javax.swing.AbstractListModel;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
 import javax.swing.JLabel;
 import javax.swing.JList;
 import javax.swing.JPanel;
@@ -20,6 +18,7 @@
 import javax.swing.JScrollPane;
 
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.data.APIDataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.gui.PrimitiveRenderer;
 
@@ -28,180 +27,83 @@
  * @since 2599
  */
 public class UploadedObjectsSummaryPanel extends JPanel {
-    /** the list with the added primitives */
-    private PrimitiveList lstAdd;
-    private JLabel lblAdd;
-    private JScrollPane spAdd;
-    /** the list with the updated primitives */
-    private PrimitiveList lstUpdate;
-    private JLabel lblUpdate;
-    private JScrollPane spUpdate;
-    /** the list with the deleted primitives */
-    private PrimitiveList lstDelete;
-    private JLabel lblDelete;
-    private JScrollPane spDelete;
+    /**
+     * Zooms to the primitive on double-click
+     */
+    private static MouseAdapter mouseListener = new MouseAdapter() {
+        @Override
+        public void mouseClicked(MouseEvent evt) {
+            if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {
+                @SuppressWarnings("unchecked")
+                JList<OsmPrimitive> list = (JList<OsmPrimitive>) evt.getSource();
+                int index = list.locationToIndex(evt.getPoint());
+                AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index)));
+            }
+        }
+    };
 
     /**
+     * A JLabel and a JList
+     */
+    private static class ListPanel extends JPanel {
+        /**
+         * Constructor
+         *
+         * @param primitives the list of primitives
+         * @param singular the singular form of the label
+         * @param plural the plural form of the label
+         */
+        ListPanel(List<OsmPrimitive> primitives, String singular, String plural) {
+            DefaultListModel<OsmPrimitive> model = new DefaultListModel<>();
+            JList<OsmPrimitive> jList = new JList<>(model);
+            primitives.forEach(model::addElement);
+            jList.setCellRenderer(new PrimitiveRenderer());
+            jList.addMouseListener(mouseListener);
+            JScrollPane scrollPane = new JScrollPane(jList);
+            JLabel label = new JLabel(trn(singular, plural, model.size(), model.size()));
+            label.setLabelFor(jList);
+            this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+            this.add(label);
+            this.add(scrollPane);
+        }
+    }
+
+    /**
      * Constructs a new {@code UploadedObjectsSummaryPanel}.
      */
     public UploadedObjectsSummaryPanel() {
-        build();
+        super(new GridBagLayout());
     }
 
-    protected void build() {
-        setLayout(new GridBagLayout());
-        PrimitiveRenderer renderer = new PrimitiveRenderer();
-        MouseAdapter mouseListener = new MouseAdapter() {
-            @Override
-            public void mouseClicked(MouseEvent evt) {
-                if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {
-                    PrimitiveList list = (PrimitiveList) evt.getSource();
-                    int index = list.locationToIndex(evt.getPoint());
-                    AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index)));
-                }
-            }
-        };
-        // initialize the three lists for uploaded primitives, but don't add them to the dialog yet, see setUploadedPrimitives()
-        //
-        lstAdd = new PrimitiveList();
-        lstAdd.setCellRenderer(renderer);
-        lstAdd.addMouseListener(mouseListener);
-        lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
-        spAdd = new JScrollPane(lstAdd);
-        lblAdd = new JLabel(tr("Objects to add:"));
-        lblAdd.setLabelFor(lstAdd);
-
-        lstUpdate = new PrimitiveList();
-        lstUpdate.setCellRenderer(renderer);
-        lstUpdate.addMouseListener(mouseListener);
-        lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
-        spUpdate = new JScrollPane(lstUpdate);
-        lblUpdate = new JLabel(tr("Objects to modify:"));
-        lblUpdate.setLabelFor(lstUpdate);
-
-        lstDelete = new PrimitiveList();
-        lstDelete.setCellRenderer(renderer);
-        lstDelete.addMouseListener(mouseListener);
-        lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
-        spDelete = new JScrollPane(lstDelete);
-        lblDelete = new JLabel(tr("Objects to delete:"));
-        lblDelete.setLabelFor(lstDelete);
-    }
-
     /**
-     * Sets the collections of primitives which will be uploaded
+     * Builds the panel
      *
-     * @param add  the collection of primitives to add
-     * @param update the collection of primitives to update
-     * @param delete the collection of primitives to delete
+     * @param toUpload the primitives to upload
      */
-    public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) {
-        lstAdd.getPrimitiveListModel().setPrimitives(add);
-        lstUpdate.getPrimitiveListModel().setPrimitives(update);
-        lstDelete.getPrimitiveListModel().setPrimitives(delete);
-
-        GridBagConstraints gcLabel = new GridBagConstraints();
-        gcLabel.fill = GridBagConstraints.HORIZONTAL;
-        gcLabel.weightx = 1.0;
-        gcLabel.weighty = 0.0;
-        gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
-
+    public void build(APIDataSet toUpload) {
         GridBagConstraints gcList = new GridBagConstraints();
         gcList.fill = GridBagConstraints.BOTH;
         gcList.weightx = 1.0;
         gcList.weighty = 1.0;
         gcList.anchor = GridBagConstraints.CENTER;
+
         removeAll();
-        int y = -1;
-        if (!add.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(), add.size()));
-            add(lblAdd, gcLabel);
-            y++;
-            gcList.gridy = y;
-            add(spAdd, gcList);
+        List<OsmPrimitive> list = toUpload.getPrimitivesToAdd();
+        if (!list.isEmpty()) {
+            gcList.gridy++;
+            add(new ListPanel(list, "{0} object to add:", "{0} objects to add:"), gcList);
         }
-        if (!update.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblUpdate.setText(trn("{0} object to modify:", "{0} objects to modify:", update.size(), update.size()));
-            add(lblUpdate, gcLabel);
-            y++;
-            gcList.gridy = y;
-            add(spUpdate, gcList);
+        list = toUpload.getPrimitivesToUpdate();
+        if (!list.isEmpty()) {
+            gcList.gridy++;
+            add(new ListPanel(list, "{0} object to modify:", "{0} objects to modify:"), gcList);
         }
-        if (!delete.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(), delete.size()));
-            add(lblDelete, gcLabel);
-            y++;
-            gcList.gridy = y;
-            add(spDelete, gcList);
+        list = toUpload.getPrimitivesToDelete();
+        if (!list.isEmpty()) {
+            gcList.gridy++;
+            add(new ListPanel(list, "{0} object to delete:", "{0} objects to delete:"), gcList);
         }
         revalidate();
+        repaint();
     }
-
-    /**
-     * Replies the number of objects to upload
-     *
-     * @return the number of objects to upload
-     */
-    public int getNumObjectsToUpload() {
-        return lstAdd.getModel().getSize()
-        + lstUpdate.getModel().getSize()
-        + lstDelete.getModel().getSize();
-    }
-
-    /**
-     * A simple list of OSM primitives.
-     */
-    static class PrimitiveList extends JList<OsmPrimitive> {
-        /**
-         * Constructs a new {@code PrimitiveList}.
-         */
-        PrimitiveList() {
-            super(new PrimitiveListModel());
-        }
-
-        public PrimitiveListModel getPrimitiveListModel() {
-            return (PrimitiveListModel) getModel();
-        }
-    }
-
-    /**
-     * A list model for a list of OSM primitives.
-     */
-    static class PrimitiveListModel extends AbstractListModel<OsmPrimitive> {
-        private transient List<OsmPrimitive> primitives;
-
-        /**
-         * Constructs a new {@code PrimitiveListModel}.
-         */
-        PrimitiveListModel() {
-            primitives = new ArrayList<>();
-        }
-
-        PrimitiveListModel(List<OsmPrimitive> primitives) {
-            setPrimitives(primitives);
-        }
-
-        public void setPrimitives(List<OsmPrimitive> primitives) {
-            this.primitives = Optional.ofNullable(primitives).orElseGet(ArrayList::new);
-            fireContentsChanged(this, 0, getSize());
-        }
-
-        @Override
-        public OsmPrimitive getElementAt(int index) {
-            if (primitives == null) return null;
-            return primitives.get(index);
-        }
-
-        @Override
-        public int getSize() {
-            if (primitives == null) return 0;
-            return primitives.size();
-        }
-    }
 }
Index: src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -1280,7 +1280,7 @@
     @Override
     public AbstractUploadDialog getUploadDialog() {
         UploadDialog dialog = UploadDialog.getUploadDialog();
-        dialog.setUploadedPrimitives(new APIDataSet(data));
+        dialog.initLifeCycle(data, new APIDataSet(data));
         return dialog;
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(working copy)
@@ -145,11 +145,20 @@
     }
 
     @Override
-    public Object getValueAt(int rowIndex, int columnIndex) {
-        if (rowIndex >= getRowCount())
-            throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
-
-        return tags.get(rowIndex);
+    public Object getValueAt(int row, int col) {
+        if (row >= getRowCount())
+            throw new IndexOutOfBoundsException("unexpected row: row=" + row);
+        if (col >= getColumnCount())
+            throw new IndexOutOfBoundsException("unexpected col: col=" + col);
+        TagModel tag = get(row);
+        switch(col) {
+            case 0:
+                return tag.getName();
+            case 1:
+                return tag.getValue();
+            default: // Do nothing
+        }
+        return null;
     }
 
     @Override
Index: src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(working copy)
@@ -21,12 +21,8 @@
 import org.openstreetmap.josm.gui.dialogs.properties.HelpAction;
 import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;
 import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
 
 /**
  * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in
@@ -90,7 +86,6 @@
         JButton btn = new JButton(action);
         pnl.add(btn);
         btn.setMargin(new Insets(0, 0, 0, 0));
-        tagTable.addComponentNotStoppingCellEditing(btn);
     }
 
     /**
@@ -147,7 +142,7 @@
      * @param presetHandler tagging preset handler
      */
     public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) {
-        this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0);
+        this(new TagEditorModel().forPrimitive(primitive), presetHandler, -1);
     }
 
     /**
@@ -188,25 +183,11 @@
     }
 
     /**
-     * Initializes the auto completion infrastructure used in this
-     * tag editor panel. {@code layer} is the data layer from whose data set
-     * tag values are proposed as auto completion items.
-     *
-     * @param layer the data layer. Must not be null.
-     * @throws IllegalArgumentException if {@code layer} is null
+     * Returns the JTable
+     * @return the JTable
      */
-    public void initAutoCompletion(OsmDataLayer layer) {
-        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
-
-        AutoCompletionManager autocomplete = AutoCompletionManager.of(layer.data);
-        AutoCompletionList acList = new AutoCompletionList();
-
-        TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor();
-        editor.setAutoCompletionManager(autocomplete);
-        editor.setAutoCompletionList(acList);
-        editor = (TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor();
-        editor.setAutoCompletionManager(autocomplete);
-        editor.setAutoCompletionList(acList);
+    public TagTable getTable() {
+        return tagTable;
     }
 
     @Override
Index: src/org/openstreetmap/josm/gui/tagging/TagTable.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/TagTable.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/TagTable.java	(working copy)
@@ -5,15 +5,11 @@
 
 import java.awt.Component;
 import java.awt.Dimension;
-import java.awt.KeyboardFocusManager;
-import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.Collections;
-import java.util.EventObject;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.swing.AbstractAction;
 import javax.swing.CellEditor;
@@ -21,20 +17,17 @@
 import javax.swing.JTable;
 import javax.swing.KeyStroke;
 import javax.swing.ListSelectionModel;
-import javax.swing.SwingUtilities;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
-import javax.swing.text.JTextComponent;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellEditor;
 
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.TagMap;
 import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.widgets.JosmTable;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -42,17 +35,9 @@
  * @since 1762
  */
 public class TagTable extends JosmTable implements EndEditListener {
-    /** the table cell editor used by this table */
-    private TagCellEditor editor;
     private final TagEditorModel model;
     private Component nextFocusComponent;
 
-    /** a list of components to which focus can be transferred without stopping
-     * cell editing this table.
-     */
-    private final CopyOnWriteArrayList<Component> doNotStopCellEditingWhenFocused = new CopyOnWriteArrayList<>();
-    private transient CellEditorRemover editorRemover;
-
     /**
      * Action to be run when the user navigates to the next cell in the table,
      * for instance by pressing TAB or ENTER. The action alters the standard
@@ -88,7 +73,7 @@
                 row++;
             } else if (col == 1 && row == getRowCount()-1) {
                 // we are at the end. Append an empty row and move the focus to its second column
-                String key = ((TagModel) model.getValueAt(row, 0)).getName();
+                String key = (String) model.getValueAt(row, 0);
                 if (!Utils.isStripEmpty(key)) {
                     model.appendNewTag();
                     col = 0;
@@ -243,7 +228,7 @@
                 cEditor.stopCellEditing();
             }
             final int rowIdx = model.getRowCount()-1;
-            if (rowIdx < 0 || !Utils.isStripEmpty(((TagModel) model.getValueAt(rowIdx, 0)).getName())) {
+            if (rowIdx < 0 || !Utils.isStripEmpty((String) model.getValueAt(rowIdx, 0))) {
                 model.appendNewTag();
             }
             requestFocusInCell(model.getRowCount()-1, 0);
@@ -322,14 +307,24 @@
     }
 
     /**
-     * initialize the table
-     * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
+     * Creates a new tag table
+     *
+     * @param model the tag editor model
+     * @param maxCharacters maximum number of characters allowed for keys and values, -1 for unlimited
      */
-    protected final void init(final int maxCharacters) {
-        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+    public TagTable(TagEditorModel model, final int maxCharacters) {
+        super(model, new TagTableColumnModelBuilder(new DefaultTableCellRenderer(), tr("Key"), tr("Value"))
+                  .setSelectionModel(model.getColumnSelectionModel()).build(),
+              model.getRowSelectionModel());
+
+        this.model = model;
+        model.setEndEditListener(this);
+
+        setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
         setRowSelectionAllowed(true);
         setColumnSelectionAllowed(true);
         setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
 
         // make ENTER behave like TAB
         //
@@ -354,30 +349,33 @@
         getActionMap().put("addTag", addAction);
 
         pasteAction = new PasteAction();
+    }
 
-        // create the table cell editor and set it to key and value columns
-        //
-        TagCellEditor tmpEditor = new TagCellEditor(maxCharacters);
-        setRowHeight(tmpEditor.getEditor().getPreferredSize().height);
-        setTagCellEditor(tmpEditor);
+    /**
+     * Sets a TableCellEditor for the keys column.
+     * @param editor the editor to set
+     */
+    public void setKeyEditor(TableCellEditor editor) {
+        getColumnModel().getColumn(0).setCellEditor(editor);
     }
 
     /**
-     * Creates a new tag table
-     *
-     * @param model the tag editor model
-     * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
+     * Sets a TableCellEditor for the values column.
+     * @param editor the editor to set
      */
-    public TagTable(TagEditorModel model, final int maxCharacters) {
-        super(model, new TagTableColumnModelBuilder(new TagCellRenderer(), tr("Key"), tr("Value"))
-                  .setSelectionModel(model.getColumnSelectionModel()).build(),
-              model.getRowSelectionModel());
-        this.model = model;
-        model.setEndEditListener(this);
-        init(maxCharacters);
+    public void setValueEditor(TableCellEditor editor) {
+        getColumnModel().getColumn(1).setCellEditor(editor);
     }
 
     @Override
+    public boolean getDragEnabled() {
+        // fix for comboboxes flashing when clicking the cell where the arrow button will be
+        // maybe a late focus request wants to focus the cb when the popup is already open?
+        // see: BasicTableUI#adjustSelection and mouseReleasedDND
+        return true;
+    }
+
+    @Override
     public Dimension getPreferredSize() {
         return getPreferredFullWidthSize();
     }
@@ -400,44 +398,6 @@
     }
 
     /**
-     * Sets the editor autocompletion list
-     * @param autoCompletionList autocompletion list
-     */
-    public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
-        if (autoCompletionList == null)
-            return;
-        if (editor != null) {
-            editor.setAutoCompletionList(autoCompletionList);
-        }
-    }
-
-    /**
-     * Sets the autocompletion manager that should be used for editing the cells
-     * @param autocomplete The {@link AutoCompletionManager}
-     */
-    public void setAutoCompletionManager(AutoCompletionManager autocomplete) {
-        if (autocomplete == null) {
-            Logging.warn("argument autocomplete should not be null. Aborting.");
-            Logging.error(new Exception());
-            return;
-        }
-        if (editor != null) {
-            editor.setAutoCompletionManager(autocomplete);
-        }
-    }
-
-    /**
-     * Gets the {@link AutoCompletionList} the cell editor is synchronized with
-     * @return The list
-     */
-    public AutoCompletionList getAutoCompletionList() {
-        if (editor != null)
-            return editor.getAutoCompletionList();
-        else
-            return null;
-    }
-
-    /**
      * Sets the next component to request focus after navigation (with tab or enter).
      * @param nextFocusComponent next component to request focus after navigation (with tab or enter)
      */
@@ -446,26 +406,6 @@
     }
 
     /**
-     * Gets the editor that is used for the table cells
-     * @return The editor that is used when the user wants to enter text into a cell
-     */
-    public TagCellEditor getTableCellEditor() {
-        return editor;
-    }
-
-    /**
-     * Inject a tag cell editor in the tag table
-     *
-     * @param editor tag cell editor
-     */
-    public void setTagCellEditor(TagCellEditor editor) {
-        endCellEditing();
-        this.editor = editor;
-        getColumnModel().getColumn(0).setCellEditor(editor);
-        getColumnModel().getColumn(1).setCellEditor(editor);
-    }
-
-    /**
      * Request the focus in a specific cell
      * @param row The row index
      * @param col The column index
@@ -475,132 +415,19 @@
         editCellAt(row, col);
         Component c = getEditorComponent();
         if (c != null) {
-            if (!c.requestFocusInWindow()) {
-                Logging.warn("Unable to request focus for " + c);
-            }
-            if (c instanceof JTextComponent) {
-                 ((JTextComponent) c).selectAll();
-            }
+            c.requestFocusInWindow();
         }
-        // there was a bug here - on older 1.6 Java versions Tab was not working
-        // after such activation. In 1.7 it works OK,
-        // previous solution of using awt.Robot was resetting mouse speed on Windows
     }
 
-    /**
-     * Marks a component that may be focused without stopping the cell editing
-     * @param component The component
-     */
-    public void addComponentNotStoppingCellEditing(Component component) {
-        if (component == null) return;
-        doNotStopCellEditingWhenFocused.addIfAbsent(component);
-    }
-
-    /**
-     * Removes a component added with {@link #addComponentNotStoppingCellEditing(Component)}
-     * @param component The component
-     */
-    public void removeComponentNotStoppingCellEditing(Component component) {
-        if (component == null) return;
-        doNotStopCellEditingWhenFocused.remove(component);
-    }
-
     @Override
-    public boolean editCellAt(int row, int column, EventObject e) {
-
-        // a snipped copied from the Java 1.5 implementation of JTable
-        //
-        if (cellEditor != null && !cellEditor.stopCellEditing())
-            return false;
-
-        if (row < 0 || row >= getRowCount() ||
-                column < 0 || column >= getColumnCount())
-            return false;
-
-        if (!isCellEditable(row, column))
-            return false;
-
-        // make sure our custom implementation of CellEditorRemover is created
-        if (editorRemover == null) {
-            KeyboardFocusManager fm =
-                KeyboardFocusManager.getCurrentKeyboardFocusManager();
-            editorRemover = new CellEditorRemover(fm);
-            fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
-        }
-
-        // delegate to the default implementation
-        return super.editCellAt(row, column, e);
-    }
-
-    @Override
     public void endCellEditing() {
-        if (isEditing()) {
-            CellEditor cEditor = getCellEditor();
-            if (cEditor != null) {
-                // First attempt to commit. If this does not work, cancel.
-                cEditor.stopCellEditing();
+        TableCellEditor cEditor = getCellEditor();
+        if (cEditor != null) {
+            // First attempt to commit. If this does not work, cancel.
+            if (!cEditor.stopCellEditing()) {
                 cEditor.cancelCellEditing();
             }
         }
     }
 
-    @Override
-    public void removeEditor() {
-        // make sure we unregister our custom implementation of CellEditorRemover
-        KeyboardFocusManager.getCurrentKeyboardFocusManager().
-        removePropertyChangeListener("permanentFocusOwner", editorRemover);
-        editorRemover = null;
-        super.removeEditor();
-    }
-
-    @Override
-    public void removeNotify() {
-        // make sure we unregister our custom implementation of CellEditorRemover
-        KeyboardFocusManager.getCurrentKeyboardFocusManager().
-        removePropertyChangeListener("permanentFocusOwner", editorRemover);
-        editorRemover = null;
-        super.removeNotify();
-    }
-
-    /**
-     * This is a custom implementation of the CellEditorRemover used in JTable
-     * to handle the client property <code>terminateEditOnFocusLost</code>.
-     *
-     * This implementation also checks whether focus is transferred to one of a list
-     * of dedicated components, see {@link TagTable#doNotStopCellEditingWhenFocused}.
-     * A typical example for such a component is a button in {@link TagEditorPanel}
-     * which isn't a child component of {@link TagTable} but which should respond to
-     * to focus transfer in a similar way to a child of TagTable.
-     *
-     */
-    class CellEditorRemover implements PropertyChangeListener {
-        private final KeyboardFocusManager focusManager;
-
-        CellEditorRemover(KeyboardFocusManager fm) {
-            this.focusManager = fm;
-        }
-
-        @Override
-        public void propertyChange(PropertyChangeEvent ev) {
-            if (!isEditing())
-                return;
-
-            Component c = focusManager.getPermanentFocusOwner();
-            while (c != null) {
-                if (c == TagTable.this)
-                    // focus remains inside the table
-                    return;
-                if (doNotStopCellEditingWhenFocused.contains(c))
-                    // focus remains on one of the associated components
-                    return;
-                else if (c instanceof Window) {
-                    if (c == SwingUtilities.getRoot(TagTable.this) && !getCellEditor().stopCellEditing()) {
-                        getCellEditor().cancelCellEditing();
-                    }
-                    break;
-                }
-                c = c.getParent();
-            }
-        }
-    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(working copy)
@@ -1,14 +1,21 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.ac;
 
+import java.awt.Component;
+import java.awt.event.MouseEvent;
 import java.awt.im.InputContext;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EventObject;
 import java.util.LinkedList;
 import java.util.Locale;
 
 import javax.swing.ComboBoxEditor;
+import javax.swing.JTable;
+import javax.swing.event.CellEditorListener;
+import javax.swing.table.TableCellEditor;
 
+import org.openstreetmap.josm.gui.util.CellEditorSupport;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -24,7 +31,7 @@
  * @param <E> the type of the combobox entries
  * @since 18173
  */
-public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {
+public class AutoCompComboBox<E> extends JosmComboBox<E> implements TableCellEditor, AutoCompListener {
 
     /** force a different keyboard input locale for the editor */
     private boolean useFixedLocale;
@@ -48,6 +55,7 @@
         setEditable(true);
         getEditorComponent().setModel(model);
         getEditorComponent().addAutoCompListener(this);
+        tableCellEditorSupport = new CellEditorSupport(this);
     }
 
     /**
@@ -104,8 +112,8 @@
      * @param elems The string items to set
      * @deprecated Has been moved to the model, where it belongs. Use
      *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
      */
     @Deprecated
     public void setPossibleItems(Collection<E> elems) {
@@ -122,8 +130,8 @@
      * @since 15011
      * @deprecated Has been moved to the model, where it belongs. Use
      *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
      */
     @Deprecated
     public void setPossibleItemsTopDown(Collection<E> elems) {
@@ -201,4 +209,78 @@
     public void autoCompPerformed(AutoCompEvent e) {
         autocomplete(e.getItem());
     }
+
+    /* ------------------------------------------------------------------------------------ */
+    /* TableCellEditor interface                                                            */
+    /* ------------------------------------------------------------------------------------ */
+
+    private transient CellEditorSupport tableCellEditorSupport;
+    private String originalValue;
+
+    @Override
+    public void addCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.addCellEditorListener(l);
+    }
+
+    protected void rememberOriginalValue(String value) {
+        this.originalValue = value;
+    }
+
+    protected void restoreOriginalValue() {
+        setText(originalValue);
+    }
+
+    @Override
+    public void removeCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.removeCellEditorListener(l);
+    }
+
+    @Override
+    public void cancelCellEditing() {
+        restoreOriginalValue();
+        tableCellEditorSupport.fireEditingCanceled();
+    }
+
+    @Override
+    public Object getCellEditorValue() {
+        return getText();
+    }
+
+    /**
+    * Returns true if <code>anEvent</code> is <b>not</b> a <code>MouseEvent</code>.  Otherwise, it
+    * returns true if the necessary number of clicks have occurred, and returns false otherwise.
+    *
+    * @param   anEvent         the event
+    * @return  true  if cell is ready for editing, false otherwise
+    * @see #shouldSelectCell
+    */
+    @Override
+    public boolean isCellEditable(EventObject anEvent) {
+        if (anEvent instanceof MouseEvent) {
+            return ((MouseEvent) anEvent).getClickCount() >= 1;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean shouldSelectCell(EventObject anEvent) {
+        if (anEvent instanceof MouseEvent) {
+            MouseEvent e = (MouseEvent) anEvent;
+            return e.getID() != MouseEvent.MOUSE_DRAGGED;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean stopCellEditing() {
+        tableCellEditorSupport.fireEditingStopped();
+        return true;
+    }
+
+    @Override
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        setText(value == null ? "" : value.toString());
+        rememberOriginalValue(getText());
+        return this;
+    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java	(working copy)
@@ -4,15 +4,19 @@
 import java.util.EventListener;
 
 /**
- * The listener interface for receiving autoComp events.
- * The class that is interested in processing an autoComp event
- * implements this interface, and the object created with that
- * class is registered with a component, using the component's
- * <code>addAutoCompListener</code> method. When the autoComp event
- * occurs, that object's <code>autoCompPerformed</code> method is
- * invoked.
+ * The listener interface for receiving AutoCompEvent events.
+ * <p>
+ * The class that is interested in processing an {@link AutoCompEvent} implements this interface,
+ * and the object created with that class is registered with an autocompleting component using the
+ * autocompleting component's {@link AutoCompTextField#addAutoCompListener addAutoCompListener}
+ * method.
+ * <p>
+ * Before the autocompletion searches for candidates, the listener's {@code autoCompBefore} method
+ * is invoked. It can be used to initialize the {@link AutoCompComboBoxModel}. After the
+ * autocompletion occured the listener's {@code autoCompPerformed} method is invoked. It is used eg.
+ * for adjusting the selection of an {@link AutoCompComboBox} after its {@link AutoCompTextField}
+ * has autocompleted.
  *
- * @see AutoCompEvent
  * @since 18221
  */
 public interface AutoCompListener extends EventListener {
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(working copy)
@@ -6,8 +6,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -16,6 +16,7 @@
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -40,7 +41,10 @@
 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.MultiMap;
@@ -47,9 +51,9 @@
 import org.openstreetmap.josm.tools.Utils;
 
 /**
- * AutoCompletionManager holds a cache of keys with a list of
- * possible auto completion values for each key.
- *
+ * AutoCompletionManager holds a cache of keys with a list of possible auto completion values for
+ * each key.
+ * <p>
  * Each DataSet can be assigned one AutoCompletionManager instance such that
  * <ol>
  *   <li>any key used in a tag in the data set is part of the key list in the cache</li>
@@ -56,12 +60,9 @@
  *   <li>any value used in a tag for a specific key is part of the autocompletion list of this key</li>
  * </ol>
  *
- * Building up auto completion lists should not
- * slow down tabbing from input field to input field. Looping through the complete
- * data set in order to build up the auto completion list for a specific input
- * field is not efficient enough, hence this cache.
- *
- * TODO: respect the relation type for member role autocompletion
+ * Building up auto completion lists should not slow down tabbing from input field to input field.
+ * Looping through the complete data set in order to build up the auto completion list for a
+ * specific input field is not efficient enough, hence this cache.
  */
 public class AutoCompletionManager implements DataSetListener {
 
@@ -105,6 +106,12 @@
         }
     }
 
+    /**
+     * Compares two AutoCompletionItems alphabetically.
+     */
+    public static final Comparator<AutoCompletionItem> ALPHABETIC_COMPARATOR =
+        (ac1, ac2) -> String.CASE_INSENSITIVE_ORDER.compare(ac1.getValue(), ac2.getValue());
+
     /** If the dirty flag is set true, a rebuild is necessary. */
     protected boolean dirty;
     /** The data set that is managed */
@@ -115,32 +122,22 @@
      * only accessed by getTagCache(), rebuild() and cachePrimitiveTags()
      * use getTagCache() accessor
      */
-    protected MultiMap<String, String> tagCache;
+    protected final MultiMap<String, String> TAG_CACHE = new MultiMap<>();
 
     /**
-     * the same as tagCache but for the preset keys and values can be accessed directly
-     */
-    static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
-
-    /**
      * Cache for tags that have been entered by the user.
      */
     static final Set<UserInputTag> USER_INPUT_TAG_CACHE = new LinkedHashSet<>();
 
     /**
-     * the cached list of member roles
-     * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles()
-     * use getRoleCache() accessor
+     * The cached relations by {@link #getRelationType(Map) relation type}.
      */
-    protected Set<String> roleCache;
+    protected final MultiMap<String, Relation> RELATION_CACHE = new MultiMap<>();
 
-    /**
-     * the same as roleCache but for the preset roles can be accessed directly
-     */
-    static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
-
     private static final Map<DataSet, AutoCompletionManager> INSTANCES = new HashMap<>();
 
+    private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
+
     /**
      * Constructs a new {@code AutoCompletionManager}.
      * @param ds data set
@@ -156,15 +153,15 @@
             rebuild();
             dirty = false;
         }
-        return tagCache;
+        return TAG_CACHE;
     }
 
-    protected Set<String> getRoleCache() {
+    protected MultiMap<String, Relation> getRelationCache() {
         if (dirty) {
             rebuild();
             dirty = false;
         }
-        return roleCache;
+        return RELATION_CACHE;
     }
 
     /**
@@ -171,8 +168,8 @@
      * initializes the cache from the primitives in the dataset
      */
     protected void rebuild() {
-        tagCache = new MultiMap<>();
-        roleCache = new HashSet<>();
+        TAG_CACHE.clear();
+        RELATION_CACHE.clear();
         cachePrimitives(ds.allNonDeletedCompletePrimitives());
     }
 
@@ -180,7 +177,8 @@
         for (OsmPrimitive primitive : primitives) {
             cachePrimitiveTags(primitive);
             if (primitive instanceof Relation) {
-                cacheRelationMemberRoles((Relation) primitive);
+                Relation rel = (Relation) primitive;
+                RELATION_CACHE.put(getRelationType(rel.getKeys()), rel);
             }
         }
     }
@@ -192,23 +190,44 @@
      * @param primitive an OSM primitive
      */
     protected void cachePrimitiveTags(OsmPrimitive primitive) {
-        primitive.visitKeys((p, key, value) -> tagCache.put(key, value));
+        primitive.visitKeys((p, key, value) -> TAG_CACHE.put(key, value));
     }
 
     /**
-     * Caches all member roles of the relation <code>relation</code>
+     * Returns the relation type.
+     * <p>
+     * This is used to categorize the relations in the dataset.  A relation with the keys:
+     * <ul>
+     * <li>type=route
+     * <li>route=hiking
+     * </ul>
+     * will return a relation type of {@code "route.hiking"}.
      *
-     * @param relation the relation
+     * @param tags the tags on the relation
+     * @return the relation type or {@code ""}
      */
-    protected void cacheRelationMemberRoles(Relation relation) {
-        for (RelationMember m: relation.getMembers()) {
-            if (m.hasRole()) {
-                roleCache.add(m.getRole());
-            }
-        }
+    private String getRelationType(Map<String, String> tags) {
+        String type = tags.get("type");
+        if (type == null) return "";
+        String subtype = tags.get(type);
+        if (subtype == null) return type;
+        return type + "." + subtype;
     }
 
     /**
+     * Construct a role out of a relation member
+     *
+     * @param member the relation member
+     * @return the Role
+     */
+    protected Role mkRole(RelationMember member) {
+        Role role = new Role();
+        role.key = member.getRole();
+        role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
+        return role;
+    }
+
+    /**
      * Remembers user input for the given key/value.
      * @param key Tag key
      * @param value Tag value
@@ -259,22 +278,42 @@
     }
 
     /**
-     * Replies the list of member roles
+     * Returns a collection of all member roles in the dataset.
+     * <p>
+     * Member roles are distinct on role name and primitive type they apply to. So there will be a
+     * role "platform" for nodes and a role "platform" for ways.
      *
-     * @return the list of member roles
+     * @return the collection of member roles
      */
-    public List<String> getMemberRoles() {
-        return new ArrayList<>(getRoleCache());
+    public Set<Role> getAllMemberRoles() {
+        return getRelationCache().getAllValues().stream()
+            .flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet());
     }
 
     /**
+     * Returns a collection of all roles in the dataset for one relation type.
+     * <p>
+     * Member roles are distinct on role name and primitive type they apply to. So there will be a
+     * role "platform" for nodes and a role "platform" for ways.
+     *
+     * @param type the {@link #getRelationType(Map) relation type}
+     * @return the collection of member roles
+     */
+    public Set<Role> getMemberRoles(String type) {
+        Set<Relation> relations = getRelationCache().get(type);
+        if (relations == null)
+            return Collections.emptySet();
+        return relations.stream().flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet());
+    }
+
+    /**
      * Populates the {@link AutoCompletionList} with the currently cached member roles.
      *
      * @param list the list to populate
      */
     public void populateWithMemberRoles(AutoCompletionList list) {
-        list.add(TaggingPresets.getPresetRoles(), AutoCompletionPriority.IS_IN_STANDARD);
-        list.add(getRoleCache(), AutoCompletionPriority.IS_IN_DATASET);
+        list.add(TaggingPresets.getPresetRoles().stream().map(r -> r.key).collect(Collectors.toList()), AutoCompletionPriority.IS_IN_STANDARD);
+        list.add(getAllMemberRoles().stream().map(role -> role.key).collect(Collectors.toSet()), AutoCompletionPriority.IS_IN_DATASET);
     }
 
     /**
@@ -303,6 +342,201 @@
     }
 
     /**
+     * Merges two or more {@code Map<String, AutoCompletionPriority>}. The result will have the
+     * priorities merged.
+     *
+     * @param maps two or more maps to merge
+     * @return the merged map
+     */
+    @SafeVarargs
+    public static final Map<String, AutoCompletionPriority> merge(Map<String, AutoCompletionPriority>... maps) {
+        return Stream.of(maps).flatMap(m -> m.entrySet().stream())
+            .collect(Collectors.toMap(Entry::getKey, Entry::getValue, AutoCompletionPriority::mergeWith));
+    }
+
+    /**
+     * Return true if the role may be applied to all of the types.
+     * <p>
+     * Returns true if {@code role.types} contains all elements of {@code types}.
+     *
+     * @param role The role.
+     * @param types The types.
+     * @return True if the role may be applied to all of the types.
+     */
+    private boolean appliesTo(Role role, Collection<TaggingPresetType> types) {
+        return role.types.containsAll(types);
+    }
+
+    /**
+     * Returns key suggestions for a given relation type.
+     * <p>
+     * Returns all keys in the dataset used on a given {@link #getRelationType(Map) relation type}.
+     *
+     * @param tags current tags in the tag editor panel, used to determine the relation type
+     * @return the suggestions
+     */
+    public Map<String, AutoCompletionPriority> getKeysForRelation(Map<String, String> tags) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
+        if (relations == null)
+            return map;
+        return relations.stream().flatMap(rel -> rel.getKeys().entrySet().stream()).map(e -> e.getKey())
+            .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
+    }
+
+    /**
+     * Returns value suggestions for a given relation type and key.
+     * <p>
+     * Returns all values in the dataset used with a given key on a given
+     * {@link #getRelationType(Map) relation type}.
+     *
+     * @param tags current tags in the tag editor panel, used to determine the relation type
+     * @param key the key to get values for
+     * @return the suggestions
+     */
+    public Map<String, AutoCompletionPriority> getValuesForRelation(Map<String, String> tags, String key) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
+        if (relations == null)
+            return map;
+        return relations.stream().map(rel -> rel.get(key)).filter(e -> e != null)
+            .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
+    }
+
+    /**
+     * Returns role suggestions for a given relation type.
+     * <p>
+     * Returns all roles in the dataset for a given {@link TaggingPresetType role type} used with a given
+     * {@link #getRelationType(Map) relation type}.
+     *
+     * @param tags current tags in the tag editor panel, used to determine the relation type
+     * @param roleTypes all roles returned will match all of the types in this set.
+     * @return the suggestions
+     */
+    public Map<String, AutoCompletionPriority> getRolesForRelation(Map<String, String> tags, EnumSet<TaggingPresetType> roleTypes) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
+        if (relations == null)
+            return map;
+        return relations.stream().flatMap(rel -> rel.getMembers().stream())
+            .map(member -> mkRole(member)).filter(role -> appliesTo(role, roleTypes))
+            .collect(Collectors.toMap(k -> k.key, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
+    }
+
+    /**
+     * Returns all presets of type {@code types} matched by {@code tags}.
+     *
+     * @param types the preset types to include, (node / way / relation ...) or null to include all types
+     * @param tags match presets using these tags or null to match all presets
+     * @return the matched presets
+     */
+    private Collection<TaggingPreset> getPresets(Collection<TaggingPresetType> types, Map<String, String> tags) {
+        if (tags == null)
+            tags = EMPTY_MAP;
+
+        Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(types, tags, false);
+        if (presets.isEmpty()) {
+            presets = TaggingPresets.getTaggingPresets();
+        }
+        return presets;
+    }
+
+    /**
+     * Returns all keys found in the presets matched by {@code tags}.
+     *
+     * @param types the preset types to include, (node / way / relation ...) or null to include all types
+     * @param tags match presets using these tags or null to match all presets
+     * @return the suggested keys
+     * @since xxx
+     */
+    public Map<String, AutoCompletionPriority> getPresetKeys(Collection<TaggingPresetType> types, Map<String, String> tags) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+
+        for (TaggingPreset preset : getPresets(types, tags)) {
+            for (TaggingPresetItem item : preset.data) {
+                if (item instanceof KeyedItem) {
+                    map.merge(((KeyedItem) item).key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+                }
+            }
+        }
+        return map;
+    }
+
+    /**
+     * Returns all values for {@code key} found in the presets matched by {@code tags}.
+     *
+     * @param types the preset types to include, (node / way / relation ...) or null to include all types
+     * @param tags match presets using these tags or null to match all presets
+     * @param key the key to return values for
+     * @return the suggested values
+     * @since xxx
+     */
+    public Map<String, AutoCompletionPriority> getPresetValues(Collection<TaggingPresetType> types, Map<String, String> tags, String key) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+
+        for (TaggingPreset preset : getPresets(types, tags)) {
+            for (TaggingPresetItem item : preset.data) {
+                if (item instanceof KeyedItem) {
+                    KeyedItem keyedItem = (KeyedItem) item;
+                    if (keyedItem.key.equals(key)) {
+                        for (String value : keyedItem.getValues()) {
+                            map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+                        }
+                    }
+                }
+            }
+        }
+        return map;
+    }
+
+    /**
+     * Returns all roles found in the presets matched by {@code tags}.
+     *
+     * @param tags match presets using these tags or null to match all presets
+     * @param roleTypes the role types to include, (node / way / relation ...) or null to include all types
+     * @return the suggested roles
+     * @since xxx
+     */
+    public Map<String, AutoCompletionPriority> getPresetRoles(Map<String, String> tags, Collection<TaggingPresetType> roleTypes) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+
+        for (TaggingPreset preset : getPresets(EnumSet.of(TaggingPresetType.RELATION), tags)) {
+            if (preset.roles != null) {
+                for (Role role : preset.roles.roles) {
+                    if (appliesTo(role, roleTypes))
+                        map.merge(role.key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+                }
+            }
+        }
+        return map;
+    }
+
+    /**
+     * Returns all cached {@link AutoCompletionItem}s for given keys.
+     *
+     * @param keys retrieve the items for these keys
+     * @return the currently cached items, sorted by priority and alphabet
+     * @since 18221
+     */
+    public List<AutoCompletionItem> getAllValuesForKeys(List<String> keys) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+
+        for (String key : keys) {
+            for (String value : TaggingPresets.getPresetValues(key)) {
+                map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+            }
+            for (String value : getDataValues(key)) {
+                map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
+            }
+            for (String value : getUserInputValues(key)) {
+                map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);
+            }
+        }
+        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
+            .sorted(ALPHABETIC_COMPARATOR).collect(Collectors.toList());
+    }
+
+    /**
      * Populates the an {@link AutoCompletionList} with the currently cached tag keys
      *
      * @param list the list to populate
@@ -345,30 +579,6 @@
     }
 
     /**
-     * Returns all cached {@link AutoCompletionItem}s for given keys.
-     *
-     * @param keys retrieve the items for these keys
-     * @return the currently cached items, sorted by priority and alphabet
-     * @since 18221
-     */
-    public List<AutoCompletionItem> getAllForKeys(List<String> keys) {
-        Map<String, AutoCompletionPriority> map = new HashMap<>();
-
-        for (String key : keys) {
-            for (String value : TaggingPresets.getPresetValues(key)) {
-                map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
-            }
-            for (String value : getDataValues(key)) {
-                map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
-            }
-            for (String value : getUserInputValues(key)) {
-                map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);
-            }
-        }
-        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList());
-    }
-
-    /**
      * Returns the currently cached tag keys.
      * @return a set of tag keys
      * @since 12859
@@ -502,8 +712,8 @@
                     ds.removeDataSetListener(AutoCompletionManager.this);
                     MainApplication.getLayerManager().removeLayerChangeListener(this);
                     dirty = true;
-                    tagCache = null;
-                    roleCache = null;
+                    TAG_CACHE.clear();
+                    RELATION_CACHE.clear();
                     ds = null;
                 }
             }
Index: src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java	(nonexistent)
+++ src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java	(working copy)
@@ -0,0 +1,62 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+/**
+ * A default autocompletion listener.
+ * @param <E> the type of the {@code AutoCompComboBox<E>} or {@code AutoCompTextField<E>}
+ */
+public class DefaultAutoCompListener<E> implements AutoCompListener, PopupMenuListener {
+    protected void updateAutoCompModel(AutoCompComboBoxModel<E> model) {
+    }
+
+    @Override
+    public void autoCompBefore(AutoCompEvent e) {
+        AutoCompTextField<E> tf = toTextField(e);
+        String savedText = tf.getText();
+        updateAutoCompModel(tf.getModel());
+        tf.setText(savedText);
+    }
+
+    @Override
+    public void autoCompPerformed(AutoCompEvent e) {
+    }
+
+    @Override
+    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+        AutoCompComboBox<E> cb = toComboBox(e);
+        String savedText = cb.getText();
+        updateAutoCompModel(cb.getModel());
+        cb.setText(savedText);
+    }
+
+    @Override
+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+    }
+
+    @Override
+    public void popupMenuCanceled(PopupMenuEvent e) {
+    }
+
+    /**
+     * Returns the AutoCompTextField that sent the request.
+     * @param e The AutoCompEvent
+     * @return the AutoCompTextField
+     */
+    @SuppressWarnings("unchecked")
+    public AutoCompTextField<E> toTextField(AutoCompEvent e) {
+        return (AutoCompTextField<E>) e.getSource();
+    }
+
+    /**
+     * Returns the AutoCompComboBox that sent the request.
+     * @param e The AutoCompEvent
+     * @return the AutoCompComboBox
+     */
+    @SuppressWarnings("unchecked")
+    public AutoCompComboBox<E> toComboBox(PopupMenuEvent e) {
+        return (AutoCompComboBox<E>) e.getSource();
+    }
+}
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(working copy)
@@ -5,7 +5,6 @@
 import static org.openstreetmap.josm.tools.I18n.trc;
 
 import java.io.File;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -22,8 +21,6 @@
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.util.LruCache;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -44,20 +41,6 @@
      */
     protected static final BooleanProperty DISPLAY_KEYS_AS_HINT = new BooleanProperty("taggingpreset.display-keys-as-hint", true);
 
-    protected void initAutoCompletionField(AutoCompletingTextField field, String... key) {
-        initAutoCompletionField(field, Arrays.asList(key));
-    }
-
-    protected void initAutoCompletionField(AutoCompletingTextField field, List<String> keys) {
-        DataSet data = OsmDataManager.getInstance().getEditDataSet();
-        if (data == null) {
-            return;
-        }
-        AutoCompletionList list = new AutoCompletionList();
-        AutoCompletionManager.of(data).populateWithTagValues(list, keys);
-        field.setAutoCompletionList(list);
-    }
-
     /**
      * Returns all cached {@link AutoCompletionItem}s for given keys.
      *
@@ -70,7 +53,7 @@
         if (data == null) {
             return Collections.emptyList();
         }
-        return AutoCompletionManager.of(data).getAllForKeys(keys);
+        return AutoCompletionManager.of(data).getAllValuesForKeys(keys);
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(working copy)
@@ -45,7 +45,7 @@
     /** cache for key/value pairs found in the preset */
     private static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
     /** cache for roles found in the preset */
-    private static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
+    private static final Map<String, Role> PRESET_ROLE_CACHE = new HashMap<>();
 
     /** The collection of listeners */
     private static final Collection<TaggingPresetListener> listeners = new ArrayList<>();
@@ -167,7 +167,7 @@
             Roles r = (Roles) item;
             for (Role i : r.roles) {
                 if (i.key != null) {
-                    PRESET_ROLE_CACHE.add(i.key);
+                    PRESET_ROLE_CACHE.put(i.key, i);
                 }
             }
         } else if (item instanceof CheckGroup) {
@@ -189,8 +189,8 @@
      * Replies a set of all roles in the tagging presets.
      * @return a set of all roles in the tagging presets.
      */
-    public static Set<String> getPresetRoles() {
-        return Collections.unmodifiableSet(PRESET_ROLE_CACHE);
+    public static Set<Role> getPresetRoles() {
+        return new HashSet<>(PRESET_ROLE_CACHE.values());
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(working copy)
@@ -178,6 +178,37 @@
         }
 
         @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            result = prime * result + ((types == null) ? 0 : types.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            Role other = (Role) obj;
+            if (key == null) {
+                if (other.key != null)
+                    return false;
+            } else if (!key.equals(other.key))
+                return false;
+            if (types == null) {
+                if (other.types != null)
+                    return false;
+            } else if (!types.equals(other.types))
+                return false;
+            return true;
+        }
+
+        @Override
         public String toString() {
             return "Role [key=" + key + ", text=" + text + ']';
         }
Index: src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java	(working copy)
@@ -39,8 +39,8 @@
      * @return the items as strings
      * @deprecated Has been moved to the model, where it belongs. Use
      *     {@link HistoryComboBoxModel#asStringList} instead. Probably you want to use
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
-     *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
+     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
      */
     @Deprecated
     public List<String> getHistory() {
Index: src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java	(working copy)
@@ -59,6 +59,8 @@
     /** greyed text to display in the editor when the selected value is empty */
     private String hint;
 
+    private boolean fakeWidth;
+
     /**
      * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model.
      * The default data model is an empty list of objects.
@@ -293,6 +295,30 @@
     }
 
     /**
+     * Make popup wider than combobox.
+     */
+    @Override
+    public Dimension getSize() {
+        Dimension dim = super.getSize();
+        if (fakeWidth)
+            dim.width = Math.max(getPreferredSize().width, dim.width);
+        return dim;
+    }
+
+    /**
+     * Helper to make popup wider than combobox.
+     */
+    @Override
+    public void doLayout() {
+        try {
+            fakeWidth = false;
+            super.doLayout();
+        } finally {
+            fakeWidth = true;
+        }
+    }
+
+    /**
      * Get the dropdown list component
      *
      * @return the list or null
@@ -383,11 +409,10 @@
         int freeAbove = bounds.y - screenBounds.y;
         int freeBelow = (screenBounds.y + screenBounds.height) - (bounds.y + bounds.height);
 
-        try {
-            // First try an implementation-dependent method to get the exact number.
-            @SuppressWarnings("unchecked")
-            JList<E> jList = getList();
-
+        int maxRowCount = 8; // default
+        @SuppressWarnings("unchecked")
+        JList<E> jList = getList();
+        if (jList != null) {
             // Calculate the free space available on screen
             Insets insets = jList.getInsets();
             // A small fudge factor that accounts for the displacement of the popup relative to the
@@ -394,8 +419,9 @@
             // combobox and the popup shadow.
             int fudge = 4;
             int free = Math.max(freeAbove, freeBelow) - (insets.top + insets.bottom) - fudge;
-            if (jList.getParent() instanceof JScrollPane) {
-                JScrollPane scroller = (JScrollPane) jList.getParent();
+            Component parent = getParent();
+            if (parent instanceof JScrollPane) {
+                JScrollPane scroller = (JScrollPane) parent;
                 Border border = scroller.getViewportBorder();
                 if (border != null) {
                     insets = border.getBorderInsets(null);
@@ -418,11 +444,10 @@
                 if (h >= free)
                     break;
             }
-            setMaximumRowCount(i);
-            // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds);
-        } catch (Exception ex) {
-            setMaximumRowCount(8); // the default
+            maxRowCount = i;
         }
+        // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds);
+        setMaximumRowCount(maxRowCount);
     }
 
     @Override
Index: src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(working copy)
@@ -205,6 +205,36 @@
     }
 
     /**
+     * Replaces all current elements with elements from the collection.
+     * <p>
+     * This is the same as {@link #removeAllElements} followed by {@link #addAllElements} but
+     * minimizes event firing and tries to keep the current selection.  Use this when all elements
+     * are reinitialized programmatically like in an {@code autoCompBefore} event.
+     *
+     * @param newElements The new elements.
+     */
+    public void replaceAllElements(Collection<E> newElements) {
+        Object oldSelected = selected;
+        int index0 = elements.size();
+        elements.clear();
+        newElements.forEach(e -> doAddElement(e));
+        int index1 = elements.size();
+        int index2 = Math.min(index0, index1);
+        if (0 < index2) {
+            fireContentsChanged(this, 0, index2 - 1);
+        }
+        if (index2 < index0) {
+            fireIntervalRemoved(this, index2, index0 - 1);
+        }
+        if (index2 < index1) {
+            fireIntervalAdded(this, index2, index1 - 1);
+        }
+        // re-select the old selection if possible
+        int index = elements.indexOf(oldSelected);
+        setSelectedItem(index == -1 ? null : getElementAt(index));
+    }
+
+    /**
      * Adds an element to the top of the list.
      * <p>
      * If the element is already in the model, moves it to the top.  If the model gets too big,
Index: src/org/openstreetmap/josm/gui/widgets/JosmTextField.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/JosmTextField.java	(revision 18287)
+++ src/org/openstreetmap/josm/gui/widgets/JosmTextField.java	(working copy)
@@ -42,7 +42,7 @@
  */
 public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener {
 
-    private final PopupMenuLauncher launcher;
+    private PopupMenuLauncher launcher;
     private String hint;
     private Icon icon;
     private Point iconPos;
@@ -241,6 +241,14 @@
     }
 
     /**
+     * Enables / disables the undo / redo functionality.
+     * @param undoRedo enable if true
+     */
+    public void enableUndoRedo(boolean undoRedo) {
+        launcher = TextContextualPopupMenu.enableMenuFor(this, undoRedo);
+    }
+
+    /**
      * Empties the internal undo manager.
      * @since 14977
      */
Index: src/org/openstreetmap/josm/tools/MultiMap.java
===================================================================
--- src/org/openstreetmap/josm/tools/MultiMap.java	(revision 18287)
+++ src/org/openstreetmap/josm/tools/MultiMap.java	(working copy)
@@ -124,6 +124,14 @@
     }
 
     /**
+     * Like getValues, but returns all values for all keys.
+     * @return the set of all values or an empty set
+     */
+    public Set<B> getAllValues() {
+        return map.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toSet());
+    }
+
+    /**
      * Returns {@code true} if this map contains no key-value mappings.
      * @return {@code true} if this map contains no key-value mappings
      * @see Map#isEmpty()
Index: test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java	(revision 18287)
+++ test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java	(working copy)
@@ -20,7 +20,6 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
 
@@ -123,9 +122,6 @@
         OsmDataLayer layer = new OsmDataLayer(ds, "test", null);
         IRelationEditor re = newRelationEditor(relation, layer);
 
-        AutoCompletingTextField tfRole = GenericRelationEditor.buildRoleTextField(re);
-        assertNotNull(tfRole);
-
         TagEditorPanel tagEditorPanel = new TagEditorPanel(relation, null);
 
         JPanel top = GenericRelationEditor.buildTagEditorPanel(tagEditorPanel);
Index: test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java	(revision 18287)
+++ test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java	(working copy)
@@ -12,6 +12,7 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest;
 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
 import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
@@ -20,7 +21,9 @@
 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
 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.gui.tagging.ac.AutoCompEvent;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -31,7 +34,7 @@
  * @author Michael Zangl
  */
 @Disabled
-public abstract class AbstractRelationEditorActionTest {
+public abstract class AbstractRelationEditorActionTest implements AutoCompListener {
     /**
      * Platform for tooltips.
      */
@@ -46,13 +49,13 @@
     private IRelationEditor editor;
     private MemberTable memberTable;
     private MemberTableModel memberTableModel;
-    private AutoCompletingTextField tfRole;
+    private AutoCompTextField<AutoCompletionItem> tfRole;
     private TagEditorModel tagModel;
 
     protected final IRelationEditorActionAccess relationEditorAccess = new IRelationEditorActionAccess() {
 
         @Override
-        public AutoCompletingTextField getTextFieldRole() {
+        public AutoCompTextField<AutoCompletionItem> getTextFieldRole() {
             return tfRole;
         }
 
@@ -109,8 +112,16 @@
         selectionTableModel = new SelectionTableModel(layer);
         selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
         editor = GenericRelationEditorTest.newRelationEditor(orig, layer);
-        tfRole = new AutoCompletingTextField();
+        tfRole = new AutoCompTextField<>();
         tagModel = new TagEditorModel();
-        memberTable = new MemberTable(layer, editor.getRelation(), memberTableModel);
+        memberTable = new MemberTable(layer, this, memberTableModel);
     }
+
+    @Override
+    public void autoCompBefore(AutoCompEvent e) {
+    }
+
+    @Override
+    public void autoCompPerformed(AutoCompEvent e) {
+    }
 }
