Index: src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 18275)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(working copy)
@@ -14,8 +14,6 @@
 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 +25,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
 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,8 +47,11 @@
 import javax.swing.JSplitPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
+import javax.swing.JTextField;
 import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.command.ChangeMembersCommand;
@@ -58,6 +63,7 @@
 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.AutoCompletionItem;
 import org.openstreetmap.josm.data.validation.tests.RelationChecker;
 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -98,16 +104,21 @@
 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.ac.AutoCompComboBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
+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.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
 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;
@@ -117,7 +128,10 @@
  * This dialog is for editing relations.
  * @since 343
  */
-public class GenericRelationEditor extends RelationEditor implements CommandQueueListener {
+public class GenericRelationEditor extends RelationEditor implements AutoCompListener, CommandQueueListener, PopupMenuListener {
+    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 +145,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 +187,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
@@ -216,21 +232,30 @@
         populateModels(relation);
         tagEditorPanel.getModel().ensureOneTag();
 
+        manager = AutoCompletionManager.of(this.getLayer().data);
+
         // setting up the member table
-        memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
+        AutoCompComboBox<AutoCompletionItem> cbRoleEditor = new AutoCompComboBox<>();
+        cbRoleEditor.getEditorComponent().addAutoCompListener(this);
+        cbRoleEditor.addPopupMenuListener(this);
+        cbRoleEditor.setToolTipText(tr("Select a role for this relation member"));
+        memberTable = new MemberTable(getLayer(), cbRoleEditor, memberTableModel);
         memberTable.addMouseListener(new MemberTableDblClickAdapter());
         memberTableModel.addMemberModelListener(memberTable);
 
-        MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
         selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
-        selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
+        selectionTable.setRowHeight(cbRoleEditor.getPreferredSize().height);
 
         LeftButtonToolbar leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess());
-        tfRole = buildRoleTextField(this);
+        cbRole = new AutoCompComboBox<>();
+        cbRole.getEditorComponent().addAutoCompListener(this);
+        cbRole.addPopupMenuListener(this);
+        cbRole.setText(Config.getPref().get(PREF_LASTROLE, ""));
+        cbRole.setToolTipText(tr("Select a role"));
 
         JSplitPane pane = buildSplitPane(
                 buildTagEditorPanel(tagEditorPanel),
-                buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
+                buildMemberEditorPanel(leftButtonToolbar),
                 this);
         pane.setPreferredSize(new Dimension(100, 100));
 
@@ -310,7 +335,7 @@
                 @Override
                 public void actionPerformed(ActionEvent e) {
                     super.actionPerformed(e);
-                    tfRole.requestFocusInWindow();
+                    cbRole.requestFocusInWindow();
                 }
             }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
         }
@@ -446,47 +471,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 +510,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 role suggestions based on relation type"));
+        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 +566,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 +574,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);
@@ -754,6 +756,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 +1044,9 @@
         }
 
         @Override
-        public AutoCompletingTextField getTextFieldRole() {
-            return tfRole;
+        public JTextField getTextFieldRole() {
+            return cbRole.getEditorComponent();
         }
-
     }
 
     @Override
@@ -1054,4 +1058,56 @@
             applyAction.updateEnabledState();
         }
     }
+
+    private void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
+        Map<String, String> currentTags = new HashMap<>();
+        if (btnFilter.isSelected())
+            currentTags = tagEditorPanel.getModel().getTags();
+        Set<Role> currentRoles = new HashSet<>();
+        EnumSet<TaggingPresetType> selectedTypes = EnumSet.noneOf(TaggingPresetType.class);
+        for (int i = 0; i < memberTableModel.getRowCount(); ++i) {
+            RelationMember member = memberTableModel.getValue(i);
+            Role role = new Role();
+            role.key = member.getRole();
+            role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
+            currentRoles.add(role);
+        }
+        for (RelationMember member : memberTableModel.getSelectedMembers()) {
+            selectedTypes.add(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
+        }
+        model.replaceAllElements(manager.getRolesForRelation(currentTags, currentRoles, selectedTypes));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void autoCompBefore(AutoCompEvent e) {
+        AutoCompTextField<AutoCompletionItem> tf = (AutoCompTextField<AutoCompletionItem>) e.getSource();
+        String savedText = tf.getText();
+        updateAutoCompModel(tf.getModel());
+        tf.setText(savedText);
+    }
+
+    @Override
+    public void autoCompPerformed(AutoCompEvent e) {
+        // Not interested
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+        AutoCompComboBox<AutoCompletionItem> cb = (AutoCompComboBox<AutoCompletionItem>) e.getSource();
+        String savedText = cb.getText();
+        updateAutoCompModel(cb.getModel());
+        cb.setText(savedText);
+    }
+
+    @Override
+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+        // Who cares?
+    }
+
+    @Override
+    public void popupMenuCanceled(PopupMenuEvent e) {
+        // Who cares?
+    }
 }
Index: src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java	(revision 18275)
+++ 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 18275)
+++ 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);
Index: src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java	(revision 18275)
+++ 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 18275)
+++ 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 18275)
+++ 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 18275)
+++ 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/tagging/ac/AutoCompComboBox.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(revision 18275)
+++ src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(working copy)
@@ -1,14 +1,20 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.ac;
 
+import java.awt.Component;
 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 +30,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 +54,7 @@
         setEditable(true);
         getEditorComponent().setModel(model);
         getEditorComponent().addAutoCompListener(this);
+        tableCellEditorSupport = new CellEditorSupport(this);
     }
 
     /**
@@ -104,8 +111,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 +129,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 +208,63 @@
     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();
+    }
+
+    @Override
+    public boolean isCellEditable(EventObject anEvent) {
+        return true;
+    }
+
+    @Override
+    public boolean shouldSelectCell(EventObject anEvent) {
+        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 18275)
+++ 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 18275)
+++ 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;
@@ -40,6 +40,7 @@
 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.TaggingPresetType;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
@@ -47,9 +48,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 +57,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 +103,10 @@
         }
     }
 
+    static final Comparator<AutoCompletionItem> ALPHABETIC_AUTOCOMPLETIONITEM_COMPARATOR =
+        (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
+
+
     /** If the dirty flag is set true, a rebuild is necessary. */
     protected boolean dirty;
     /** The data set that is managed */
@@ -128,17 +130,12 @@
     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 list of member roles by relation {@code type}.
+     * <p>
+     * Used by rebuild() and cacheRelationMemberRoles().  Use the {@link #getRoleCache} accessor.
      */
-    protected Set<String> roleCache;
+    protected final MultiMap<String, Role> ROLE_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<>();
 
     /**
@@ -159,12 +156,12 @@
         return tagCache;
     }
 
-    protected Set<String> getRoleCache() {
+    protected MultiMap<String, Role> getRoleCache() {
         if (dirty) {
             rebuild();
             dirty = false;
         }
-        return roleCache;
+        return ROLE_CACHE;
     }
 
     /**
@@ -172,7 +169,7 @@
      */
     protected void rebuild() {
         tagCache = new MultiMap<>();
-        roleCache = new HashSet<>();
+        ROLE_CACHE.clear();
         cachePrimitives(ds.allNonDeletedCompletePrimitives());
     }
 
@@ -196,14 +193,39 @@
     }
 
     /**
+     * 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 tags the tags on the relation
+     * @return the relation type or {@code ""}
+     */
+    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;
+    }
+
+    /**
      * Caches all member roles of the relation <code>relation</code>
      *
      * @param relation the relation
      */
     protected void cacheRelationMemberRoles(Relation relation) {
-        for (RelationMember m: relation.getMembers()) {
-            if (m.hasRole()) {
-                roleCache.add(m.getRole());
+        String type = getRelationType(relation.getKeys());
+        for (RelationMember member: relation.getMembers()) {
+            if (type != null && member.hasRole()) {
+                Role role = new Role();
+                role.key = member.getRole();
+                role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
+                ROLE_CACHE.put(type, role);
             }
         }
     }
@@ -263,8 +285,8 @@
      *
      * @return the list of member roles
      */
-    public List<String> getMemberRoles() {
-        return new ArrayList<>(getRoleCache());
+    public List<Role> getMemberRoles() {
+        return new ArrayList<>(getRoleCache().getAllValues());
     }
 
     /**
@@ -273,8 +295,8 @@
      * @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(getRoleCache().getAllValues().stream().map(r -> r.key).collect(Collectors.toList()), AutoCompletionPriority.IS_IN_DATASET);
     }
 
     /**
@@ -303,6 +325,72 @@
     }
 
     /**
+     * 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, EnumSet<TaggingPresetType> types) {
+        return role.types.containsAll(types);
+    }
+
+    /**
+     * Returns all suitable roles for a given relation member.
+     * <p>
+     * This function implements the relation role filter. It gets the suitable roles from
+     * <ul>
+     * <li>matching tagging presets and from
+     * <li>relations of the same type in the dataset.
+     * </ul>
+     * If no matching preset is found, returns all roles of all presets.
+     *
+     * @param keys current keys in the tag editor panel, used to determine the relation type
+     * @param roles all roles of all members in the member editor panel
+     * @param types the union of the types of the selected roles in the member editor panel
+     * @return list of {@link AutoCompletionItem}s, alphabetically sorted
+     * @since xxx
+     */
+    public List<AutoCompletionItem> getRolesForRelation(Map<String, String> keys, Set<Role> roles, EnumSet<TaggingPresetType> types) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>();
+
+        // always add the empty role
+        map.merge("", AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+
+        // harvest the dataset
+        // the roles already present in the member editor
+        for (Role role : roles) {
+            if (appliesTo(role, types))
+                map.merge(role.key, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
+        }
+        // the roles on existing relations of the same type (or all relations)
+        String type = getRelationType(keys);
+        for (Role role : type.isEmpty() ? getRoleCache().getAllValues() : getRoleCache().getValues(type)) {
+            if (appliesTo(role, types))
+                map.merge(role.key, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
+        }
+
+        // harvest the presets
+        Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(null, keys, false);
+        if (presets.isEmpty()) {
+            presets = TaggingPresets.getTaggingPresets();
+        }
+        // all roles of all matched presets (or all presets)
+        for (TaggingPreset preset : presets) {
+            if (preset.roles != null) {
+                for (Role role : preset.roles.roles) {
+                    if (appliesTo(role, types))
+                        map.merge(role.key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
+                }
+            }
+        }
+        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
+            .sorted(ALPHABETIC_AUTOCOMPLETIONITEM_COMPARATOR).collect(Collectors.toList());
+    }
+
+    /**
      * Populates the an {@link AutoCompletionList} with the currently cached tag keys
      *
      * @param list the list to populate
@@ -503,7 +591,7 @@
                     MainApplication.getLayerManager().removeLayerChangeListener(this);
                     dirty = true;
                     tagCache = null;
-                    roleCache = null;
+                    ROLE_CACHE.clear();
                     ds = null;
                 }
             }
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 18275)
+++ 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.
      *
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
===================================================================
--- src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(revision 18275)
+++ 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 18275)
+++ 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 18275)
+++ 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/JosmComboBoxModel.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(revision 18275)
+++ src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(working copy)
@@ -54,12 +54,19 @@
      * {@link javax.swing.DefaultComboBoxModel}.
      *
      * @param element the element to get the index of
-     * @return an int representing the index position, where 0 is the first position
+     * @return the index of the first occurrence of the specified element in this model,
+     *         or -1 if this model does not contain the element
      */
     public int getIndexOf(E element) {
         return elements.indexOf(element);
     }
 
+    protected void doAddElement(E element) {
+        if (element != null && (maxSize == -1 || getSize() < maxSize)) {
+            elements.add(element);
+        }
+    }
+
     //
     // interface java.lang.Iterable
     //
@@ -78,14 +85,22 @@
      */
     @Override
     public void addElement(E element) {
-        if (element != null && (maxSize == -1 || getSize() < maxSize)) {
-            elements.add(element);
-        }
+        doAddElement(element);
+        fireIntervalAdded(this, elements.size() - 1, elements.size() - 1);
     }
 
     @Override
     public void removeElement(Object elem) {
-        elements.remove(elem);
+        int index = elements.indexOf(elem);
+        if (elem == selected) {
+            if (index == 0) {
+                setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1));
+            } else {
+                setSelectedItem(getElementAt(index - 1));
+            }
+        }
+        if (elements.remove(elem))
+            fireIntervalRemoved(this, index, index);
     }
 
     @Override
@@ -114,6 +129,7 @@
             removeElementAt(getSize() - 1);
         }
         elements.add(index, element);
+        fireIntervalAdded(this, index, index);
     }
 
     //
@@ -166,7 +182,11 @@
      * @param elems The elements to add.
      */
     public void addAllElements(Collection<E> elems) {
-        elems.forEach(e -> addElement(e));
+        int index0 = elements.size();
+        elems.forEach(e -> doAddElement(e));
+        int index1 = elements.size() - 1;
+        if (index0 <= index1)
+            fireIntervalAdded(this, index0, index1);
     }
 
     /**
@@ -177,10 +197,44 @@
      *               {@code String}.
      */
     public void addAllElements(Collection<String> strings, Function<String, E> buildE) {
-        strings.forEach(s -> addElement(buildE.apply(s)));
+        int index0 = elements.size();
+        strings.forEach(s -> doAddElement(buildE.apply(s)));
+        int index1 = elements.size() - 1;
+        if (index0 <= index1)
+            fireIntervalAdded(this, index0, index1);
     }
 
     /**
+     * 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,
@@ -204,11 +258,10 @@
      */
     public void removeAllElements() {
         if (!elements.isEmpty()) {
-            int firstIndex = 0;
             int lastIndex = elements.size() - 1;
             elements.clear();
             selected = null;
-            fireIntervalRemoved(this, firstIndex, lastIndex);
+            fireIntervalRemoved(this, 0, lastIndex);
         } else {
             selected = null;
         }
Index: src/org/openstreetmap/josm/tools/MultiMap.java
===================================================================
--- src/org/openstreetmap/josm/tools/MultiMap.java	(revision 18275)
+++ 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 18275)
+++ 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 18275)
+++ 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) {
+    }
 }
