Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java	(revision 18221)
@@ -22,6 +22,4 @@
 import java.util.stream.IntStream;
 
-import javax.swing.AbstractListModel;
-import javax.swing.ComboBoxModel;
 import javax.swing.DefaultListSelectionModel;
 import javax.swing.JOptionPane;
@@ -42,4 +40,5 @@
 import org.openstreetmap.josm.gui.util.ChangeNotifier;
 import org.openstreetmap.josm.gui.util.TableHelper;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
@@ -833,5 +832,5 @@
     }
 
-    public class ComparePairListModel extends AbstractListModel<ComparePairType> implements ComboBoxModel<ComparePairType> {
+    public class ComparePairListModel extends JosmComboBoxModel<ComparePairType> {
 
         private int selectedIdx;
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java	(revision 18221)
@@ -13,5 +13,4 @@
 
 import javax.swing.AbstractCellEditor;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.JLabel;
 import javax.swing.JList;
@@ -22,4 +21,5 @@
 
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -50,5 +50,5 @@
     /** the combo box used as editor */
     private final JosmComboBox<Object> editor;
-    private final DefaultComboBoxModel<Object> editorModel;
+    private final JosmComboBoxModel<Object> editorModel;
     private final CopyOnWriteArrayList<NavigationListener> listeners;
 
@@ -87,5 +87,5 @@
      */
     public MultiValueCellEditor() {
-        editorModel = new DefaultComboBoxModel<>();
+        editorModel = new JosmComboBoxModel<>();
         editor = new JosmComboBox<Object>(editorModel) {
             @Override
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java	(revision 18221)
@@ -7,5 +7,4 @@
 import java.awt.Font;
 
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.ImageIcon;
 import javax.swing.JLabel;
@@ -16,4 +15,5 @@
 import org.openstreetmap.josm.gui.conflict.ConflictColors;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Logging;
@@ -27,5 +27,5 @@
     private final ImageIcon iconDecided;
     private final ImageIcon iconUndecided;
-    private final DefaultComboBoxModel<Object> model;
+    private final JosmComboBoxModel<Object> model;
     private final JosmComboBox<Object> cbDecisionRenderer;
 
@@ -37,5 +37,5 @@
         iconDecided = ImageProvider.get("dialogs/conflict", "tagconflictresolved");
         iconUndecided = ImageProvider.get("dialogs/conflict", "tagconflictunresolved");
-        model = new DefaultComboBoxModel<>();
+        model = new JosmComboBoxModel<>();
         cbDecisionRenderer = new JosmComboBox<>(model);
     }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 18221)
@@ -7,4 +7,5 @@
 import java.awt.BorderLayout;
 import java.awt.Component;
+import java.awt.ComponentOrientation;
 import java.awt.Container;
 import java.awt.Cursor;
@@ -14,9 +15,7 @@
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.Transferable;
 import java.awt.event.ActionEvent;
-import java.awt.event.FocusAdapter;
 import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
@@ -45,6 +44,4 @@
 import javax.swing.Box;
 import javax.swing.ButtonGroup;
-import javax.swing.ComboBoxModel;
-import javax.swing.DefaultListCellRenderer;
 import javax.swing.ImageIcon;
 import javax.swing.JCheckBoxMenuItem;
@@ -61,6 +58,7 @@
 import javax.swing.ListCellRenderer;
 import javax.swing.SwingUtilities;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 import javax.swing.table.DefaultTableModel;
-import javax.swing.text.JTextComponent;
 
 import org.openstreetmap.josm.actions.JosmAction;
@@ -87,9 +85,12 @@
 import org.openstreetmap.josm.gui.IExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
+import org.openstreetmap.josm.gui.widgets.OrientationAction;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
 import org.openstreetmap.josm.io.XmlWriter;
@@ -116,5 +117,4 @@
 
     private String changedKey;
-    private String objKey;
 
     static final Comparator<AutoCompletionItem> DEFAULT_AC_ITEM_COMPARATOR =
@@ -125,5 +125,4 @@
     /** Maximum number of recent tags */
     public static final int MAX_LRU_TAGS_NUMBER = 30;
-
     /** Autocomplete keys by default */
     public static final BooleanProperty AUTOCOMPLETE_KEYS = new BooleanProperty("properties.autocomplete-keys", true);
@@ -193,4 +192,36 @@
 
     /**
+     * A custom list cell renderer that adds the value count to some items.
+     */
+    static class TEHListCellRenderer extends JosmListCellRenderer<AutoCompletionItem> {
+        protected Map<String, Integer> map;
+
+        TEHListCellRenderer(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 there is a value count add it to the text
+            if (map != null) {
+                String text = value == null ? "" : value.toString();
+                count = map.get(text);
+                if (count != null) {
+                    value = new AutoCompletionItem(tr("{0} ({1})", text, count));
+                }
+            }
+            Component l = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+            l.setComponentOrientation(component.getComponentOrientation());
+            if (count != null) {
+                l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD));
+            }
+            return l;
+        }
+    }
+
+    /**
      * Constructs a new {@code TagEditHelper}.
      * @param tagTable tag table
@@ -284,8 +315,5 @@
             return;
 
-        String key = getDataKey(row);
-        objKey = key;
-
-        final IEditTagDialog editDialog = getEditTagDialog(row, focusOnKey, key);
+        final IEditTagDialog editDialog = getEditTagDialog(row, focusOnKey, getDataKey(row));
         editDialog.showDialog();
         if (editDialog.getValue() != 1)
@@ -444,27 +472,7 @@
         private final transient Map<String, Integer> m;
         private final transient Comparator<AutoCompletionItem> usedValuesAwareComparator;
-
-        private final transient ListCellRenderer<AutoCompletionItem> cellRenderer = new ListCellRenderer<AutoCompletionItem>() {
-            private final DefaultListCellRenderer def = new DefaultListCellRenderer();
-            @Override
-            public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list,
-                    AutoCompletionItem value, int index, boolean isSelected, boolean cellHasFocus) {
-                Component c = def.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-                if (c instanceof JLabel) {
-                    String str = value.getValue();
-                    if (valueCount.containsKey(objKey)) {
-                        Map<String, Integer> map = valueCount.get(objKey);
-                        if (map.containsKey(str)) {
-                            str = tr("{0} ({1})", str, map.get(str));
-                            c.setFont(c.getFont().deriveFont(Font.ITALIC + Font.BOLD));
-                        }
-                    }
-                    ((JLabel) c).setText(str);
-                }
-                return c;
-            }
-        };
-
-        protected EditTagDialog(String key, Map<String, Integer> map, final boolean initialFocusOnKey) {
+        private final transient AutoCompletionManager autocomplete;
+
+        protected EditTagDialog(String key, Map<String, Integer> map, boolean initialFocusOnKey) {
             super(MainApplication.getMainFrame(), trn("Change value?", "Change values?", map.size()), tr("OK"), tr("Cancel"));
             setButtonIcons("ok", "cancel");
@@ -473,4 +481,5 @@
             this.key = key;
             this.m = map;
+            this.initialFocusOnKey = initialFocusOnKey;
 
             usedValuesAwareComparator = (o1, o2) -> {
@@ -493,16 +502,32 @@
             mainPanel.add(new JLabel(msg), BorderLayout.NORTH);
 
-            JPanel p = new JPanel(new GridBagLayout());
+            JPanel p = new JPanel(new GridBagLayout()) {
+                /**
+                 * This hack allows the comboboxes to have their own orientation.
+                 *
+                 * The problem is that
+                 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls
+                 * {@code applyComponentOrientation} very late in the dialog construction process
+                 * thus overwriting the orientation the components have chosen for themselves.
+                 *
+                 * This stops the propagation of {@code applyComponentOrientation}, thus all
+                 * components may (and have to) set their own orientation.
+                 */
+                @Override
+                public void applyComponentOrientation(ComponentOrientation o) {
+                    setComponentOrientation(o);
+                }
+            };
             mainPanel.add(p, BorderLayout.CENTER);
 
-            AutoCompletionManager autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());
+            autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());
             List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR);
 
             keys = new AutoCompComboBox<>();
             keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable
-            keys.setPrototypeDisplayValue(new AutoCompletionItem(key));
             keys.setEditable(true);
+            keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
             keys.getModel().addAllElements(keyList);
-            keys.setSelectedItem(key);
+            keys.setSelectedItemText(key);
 
             p.add(Box.createVerticalStrut(5), GBC.eol());
@@ -517,10 +542,9 @@
             values = new AutoCompComboBox<>();
             values.getModel().setComparator(Comparator.naturalOrder());
-            values.setPrototypeDisplayValue(new AutoCompletionItem(selection));
-            values.setRenderer(cellRenderer);
+            values.setRenderer(new TEHListCellRenderer(values, values.getRenderer(), valueCount.get(key)));
             values.setEditable(true);
+            values.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
             values.getModel().addAllElements(valueList);
-            values.setSelectedItem(selection);
-            values.getEditor().setItem(selection);
+            values.setSelectedItemText(selection);
 
             p.add(Box.createVerticalStrut(5), GBC.eol());
@@ -528,21 +552,23 @@
             p.add(Box.createHorizontalStrut(10), GBC.std());
             p.add(values, GBC.eol().fill(GBC.HORIZONTAL));
-            values.getEditor().addActionListener(e -> buttonAction(0, null));
-            addFocusAdapter(autocomplete, usedValuesAwareComparator);
-
-            addUpdateIconListener();
+            p.add(Box.createVerticalStrut(2), GBC.eol());
+
+            p.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation());
+            keys.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+            values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText()));
 
             setContent(mainPanel, false);
 
-            addWindowListener(new WindowAdapter() {
-                @Override
-                public void windowOpened(WindowEvent e) {
-                    if (initialFocusOnKey) {
-                        selectKeysComboBox();
-                    } else {
-                        selectValuesCombobox();
-                    }
-                }
-            });
+            addEventListeners();
+        }
+
+        @Override
+        public void autoCompBefore(AutoCompEvent e) {
+            updateValueModel(autocomplete, usedValuesAwareComparator);
+        }
+
+        @Override
+        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+            updateValueModel(autocomplete, usedValuesAwareComparator);
         }
 
@@ -600,7 +626,12 @@
     }
 
-    protected abstract class AbstractTagsDialog extends ExtendedDialog {
+    protected abstract class AbstractTagsDialog extends ExtendedDialog implements AutoCompListener, FocusListener, PopupMenuListener {
         protected AutoCompComboBox<AutoCompletionItem> keys;
         protected AutoCompComboBox<AutoCompletionItem> values;
+        protected boolean initialFocusOnKey = true;
+        /**
+         * The 'values' model is currently holding values for this key. Used for lazy-loading of values.
+         */
+        protected String currentValuesModelKey = "";
 
         AbstractTagsDialog(Component parent, String title, String... buttonTexts) {
@@ -623,4 +654,5 @@
             setRememberWindowGeometry(getClass().getName() + ".geometry",
                 WindowGeometry.centerInWindow(MainApplication.getMainFrame(), size));
+            keys.setFixedLocale(PROPERTY_FIX_TAG_LOCALE.get());
         }
 
@@ -642,5 +674,4 @@
                     rememberWindowGeometry(geometry);
                 }
-                keys.setFixedLocale(PROPERTY_FIX_TAG_LOCALE.get());
                 updateOkButtonIcon();
             }
@@ -648,69 +679,69 @@
         }
 
-        private void selectACComboBoxSavingUnixBuffer(AutoCompComboBox<AutoCompletionItem> cb) {
-            // select combobox with saving unix system selection (middle mouse paste)
-            Clipboard sysSel = ClipboardUtils.getSystemSelection();
-            if (sysSel != null) {
-                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
-                cb.requestFocusInWindow();
-                cb.getEditor().selectAll();
-                if (old != null) {
-                    sysSel.setContents(old, null);
-                }
-            } else {
-                cb.requestFocusInWindow();
-                cb.getEditor().selectAll();
-            }
-        }
-
-        public void selectKeysComboBox() {
-            selectACComboBoxSavingUnixBuffer(keys);
-        }
-
-        public void selectValuesCombobox() {
-            selectACComboBoxSavingUnixBuffer(values);
-        }
-
         /**
-        * Create a focus handling adapter and apply in to the editor component of value
-        * autocompletion box.
-        * @param autocomplete Manager handling the autocompletion
-        * @param comparator Class to decide what values are offered on autocompletion
-        * @return The created adapter
-        */
-        protected FocusAdapter addFocusAdapter(final AutoCompletionManager autocomplete, final Comparator<AutoCompletionItem> comparator) {
-           // get the combo box' editor component
-           final JTextComponent editor = values.getEditorComponent();
-           // Refresh the values model when focus is gained
-           FocusAdapter focus = new FocusAdapter() {
-               @Override
-               public void focusGained(FocusEvent e) {
-                   Logging.trace("Focus gained by {0}, e={1}", values, e);
-                   String key = keys.getEditor().getItem().toString();
-                   List<AutoCompletionItem> correctItems = autocomplete.getTagValues(getAutocompletionKeys(key), comparator);
-                   ComboBoxModel<AutoCompletionItem> currentModel = values.getModel();
-                   final int size = correctItems.size();
-                   boolean valuesOK = size == currentModel.getSize()
-                           && IntStream.range(0, size).allMatch(i -> Objects.equals(currentModel.getElementAt(i), correctItems.get(i)));
-                   if (!valuesOK) {
-                       values.getModel().removeAllElements();
-                       values.getModel().addAllElements(correctItems);
-                   }
-                   if (!Objects.equals(key, objKey)) {
-                       values.getEditor().selectAll();
-                       objKey = key;
-                   }
-               }
-           };
-           editor.addFocusListener(focus);
-           return focus;
-        }
-
-        protected void addUpdateIconListener() {
-            keys.addActionListener(ignore -> updateOkButtonIcon());
-            values.addActionListener(ignore -> updateOkButtonIcon());
-        }
-
-        private void updateOkButtonIcon() {
+         * Updates the values model if the key has changed
+         *
+         * @param autocomplete the autocompletion manager
+         * @param comparator sorting order for the items in the combo dropdown
+         */
+        protected void updateValueModel(AutoCompletionManager autocomplete, Comparator<AutoCompletionItem> comparator) {
+            String key = keys.getText();
+            if (!key.equals(currentValuesModelKey)) {
+                Logging.debug("updateValueModel: lazy loading values for key ''{0}''", key);
+                // key has changed, reload model
+                String savedText = values.getText();
+                values.getModel().removeAllElements();
+                values.getModel().addAllElements(autocomplete.getTagValues(getAutocompletionKeys(key), comparator));
+                values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key));
+                values.setSelectedItemText(savedText);
+                values.getEditor().selectAll();
+                currentValuesModelKey = key;
+            }
+        }
+
+        protected void addEventListeners() {
+            // OK on Enter in values
+            values.getEditor().addActionListener(e -> buttonAction(0, null));
+            // update values orientation according to key
+            keys.getEditorComponent().addFocusListener(this);
+            // update the "values" data model before an autocomplete or list dropdown
+            values.getEditorComponent().addAutoCompListener(this);
+            values.addPopupMenuListener(this);
+            // set the initial focus to either combobox
+            addWindowListener(new WindowAdapter() {
+                @Override
+                public void windowOpened(WindowEvent e) {
+                    if (initialFocusOnKey) {
+                        keys.requestFocus();
+                    } else {
+                        values.requestFocus();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void autoCompPerformed(AutoCompEvent e) {
+        }
+
+        @Override
+        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+        }
+
+        @Override
+        public void popupMenuCanceled(PopupMenuEvent e) {
+        }
+
+        @Override
+        public void focusGained(FocusEvent e) {
+        }
+
+        @Override
+        public void focusLost(FocusEvent e) {
+            // update the values combobox orientation if the key changed
+            values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText()));
+        }
+
+        protected void updateOkButtonIcon() {
             if (buttons.isEmpty()) {
                 return;
@@ -746,5 +777,4 @@
     protected class AddTagsDialog extends AbstractTagsDialog {
         private final List<JosmAction> recentTagsActions = new ArrayList<>();
-        protected final transient FocusAdapter focus;
         private final JPanel mainPanel;
         private JPanel recentTagsPanel;
@@ -752,4 +782,5 @@
         // Counter of added commands for possible undo
         private int commandCount;
+        private final transient AutoCompletionManager autocomplete;
 
         protected AddTagsDialog() {
@@ -759,20 +790,44 @@
             configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */);
 
-            mainPanel = new JPanel(new GridBagLayout());
-            keys = new AutoCompComboBox<>();
-            values = new AutoCompComboBox<>();
-            keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable
-            values.getModel().setComparator(Comparator.naturalOrder());
-            keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
-            values.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
-            keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get());
-            values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get());
-
+            mainPanel = new JPanel(new GridBagLayout()) {
+                /**
+                 * This hack allows the comboboxes to have their own orientation.
+                 *
+                 * The problem is that
+                 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls
+                 * {@code applyComponentOrientation} very late in the dialog construction process
+                 * thus overwriting the orientation the components have chosen for themselves.
+                 *
+                 * This stops the propagation of {@code applyComponentOrientation}, thus all
+                 * components may (and have to) set their own orientation.
+                 */
+                @Override
+                public void applyComponentOrientation(ComponentOrientation o) {
+                    setComponentOrientation(o);
+                }
+            };
             mainPanel.add(new JLabel("<html>"+trn("This will change up to {0} object.",
                 "This will change up to {0} objects.", sel.size(), sel.size())
                 +"<br><br>"+tr("Please select a key")), GBC.eol().fill(GBC.HORIZONTAL));
 
+            keys = new AutoCompComboBox<>();
+            keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
+            keys.setEditable(true);
+            keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable
+            keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get());
+
+            mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL));
+            mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol());
+
+            values = new AutoCompComboBox<>();
+            values.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
+            values.setEditable(true);
+            values.getModel().setComparator(Comparator.naturalOrder());
+            values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get());
+
+            mainPanel.add(values, GBC.eop().fill(GBC.HORIZONTAL));
+
             cacheRecentTags();
-            AutoCompletionManager autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());
+            autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());
             List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR);
 
@@ -780,13 +835,7 @@
             keyList.removeIf(item -> containsDataKey(item.getValue()));
 
-            keys.getModel().removeAllElements();
             keys.getModel().addAllElements(keyList);
-            keys.setEditable(true);
-
-            mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL));
-
-            mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol());
-            values.setEditable(true);
-            mainPanel.add(values, GBC.eop().fill(GBC.HORIZONTAL));
+
+            updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR);
 
             // pre-fill first recent tag for which the key is not already present
@@ -795,13 +844,11 @@
                     .findFirst()
                     .ifPresent(tag -> {
-                        keys.setSelectedItem(tag.getKey());
-                        values.setSelectedItem(tag.getValue());
+                        keys.setSelectedItemText(tag.getKey());
+                        values.setSelectedItemText(tag.getValue());
                     });
 
-            focus = addFocusAdapter(autocomplete, DEFAULT_AC_ITEM_COMPARATOR);
-            // fire focus event in advance or otherwise the popup list will be too small at first
-            focus.focusGained(new FocusEvent(this, FocusEvent.FOCUS_GAINED));
-
-            addUpdateIconListener();
+
+            keys.addActionListener(ignore -> updateOkButtonIcon());
+            values.addActionListener(ignore -> updateOkButtonIcon());
 
             // Add tag on Shift-Enter
@@ -813,5 +860,5 @@
                         performTagAdding();
                         refreshRecentTags();
-                        selectKeysComboBox();
+                        keys.requestFocus();
                     }
                 });
@@ -820,7 +867,9 @@
 
             mainPanel.add(Box.createVerticalGlue(), GBC.eop().fill());
+            mainPanel.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation());
+
             setContent(mainPanel, false);
 
-            selectKeysComboBox();
+            addEventListeners();
 
             popupMenu.add(new AbstractAction(tr("Set number of recently added tags")) {
@@ -847,4 +896,14 @@
             rememberLastTags.setState(PROPERTY_REMEMBER_TAGS.get());
             popupMenu.add(rememberLastTags);
+        }
+
+        @Override
+        public void autoCompBefore(AutoCompEvent e) {
+            updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR);
+        }
+
+        @Override
+        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+            updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR);
         }
 
@@ -972,9 +1031,9 @@
                     @Override
                     public void actionPerformed(ActionEvent e) {
-                        keys.setSelectedItem(t.getKey());
+                        keys.setSelectedItemText(t.getKey());
                         // fix #7951, #8298 - update list of values before setting value (?)
-                        focus.focusGained(new FocusEvent(AddTagsDialog.this, FocusEvent.FOCUS_GAINED));
-                        values.setSelectedItem(t.getValue());
-                        selectValuesCombobox();
+                        updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR);
+                        values.setSelectedItemText(t.getValue());
+                        values.requestFocus();
                     }
                 };
@@ -989,5 +1048,5 @@
                         performTagAdding();
                         refreshRecentTags();
-                        selectKeysComboBox();
+                        keys.requestFocus();
                     }
                 };
@@ -1037,5 +1096,5 @@
                                 performTagAdding();
                                 refreshRecentTags();
-                                selectKeysComboBox();
+                                keys.requestFocus();
                             } else if (e.getClickCount() > 1) {
                                 // add tags and close window on double-click
Index: /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 18221)
@@ -108,5 +108,5 @@
 
         hcbUploadComment.setToolTipText(tr("Enter an upload comment"));
-        hcbUploadComment.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
+        hcbUploadComment.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
         JTextField editor = hcbUploadComment.getEditorComponent();
         editor.getDocument().putProperty("tag", "comment");
@@ -147,5 +147,5 @@
 
         hcbUploadSource.setToolTipText(tr("Enter a source"));
-        hcbUploadSource.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
+        hcbUploadSource.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
         JTextField editor = hcbUploadSource.getEditorComponent();
         editor.getDocument().putProperty("tag", "source");
Index: /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java	(revision 18221)
@@ -5,6 +5,4 @@
 import java.util.List;
 
-import javax.swing.DefaultComboBoxModel;
-
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.ChangesetCache;
@@ -12,4 +10,5 @@
 import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
 import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -19,5 +18,5 @@
  *
  */
-public class OpenChangesetComboBoxModel extends DefaultComboBoxModel<Changeset> implements ChangesetCacheListener {
+public class OpenChangesetComboBoxModel extends JosmComboBoxModel<Changeset> implements ChangesetCacheListener {
     private final transient List<Changeset> changesets;
     private transient Changeset selectedChangeset;
@@ -79,5 +78,5 @@
 
     @Override
-    public int getIndexOf(Object anObject) {
+    public int getIndexOf(Changeset anObject) {
         return changesets.indexOf(anObject);
     }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 18221)
@@ -40,5 +40,4 @@
 import javax.swing.JPanel;
 import javax.swing.JSeparator;
-import javax.swing.MutableComboBoxModel;
 import javax.swing.SwingConstants;
 import javax.swing.event.ChangeEvent;
@@ -71,4 +70,5 @@
 import org.openstreetmap.josm.gui.layer.gpx.GpxDataHelper;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -86,5 +86,5 @@
 public class CorrelateGpxWithImages extends AbstractAction implements ExpertModeChangeListener, Destroyable {
 
-    private static MutableComboBoxModel<GpxDataWrapper> gpxModel;
+    private static JosmComboBoxModel<GpxDataWrapper> gpxModel;
     private static boolean forceTags;
 
@@ -424,5 +424,5 @@
      */
     private void constructGpxModel(NoGpxDataWrapper nogdw) {
-        gpxModel = new DefaultComboBoxModel<>();
+        gpxModel = new JosmComboBoxModel<>();
         GpxDataWrapper defaultItem = null;
         for (AbstractModifiableLayer cur : MainApplication.getLayerManager().getLayersOfType(AbstractModifiableLayer.class)) {
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/display/LanguagePreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/display/LanguagePreference.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/display/LanguagePreference.java	(revision 18221)
@@ -12,5 +12,4 @@
 
 import javax.swing.Box;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.DefaultListCellRenderer;
 import javax.swing.JLabel;
@@ -25,4 +24,5 @@
 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.GBC;
@@ -82,5 +82,5 @@
     }
 
-    private static class LanguageComboBoxModel extends DefaultComboBoxModel<Locale> {
+    private static class LanguageComboBoxModel extends JosmComboBoxModel<Locale> {
         private final List<Locale> data = new ArrayList<>();
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java	(revision 18221)
@@ -2,11 +2,4 @@
 package org.openstreetmap.josm.gui.tagging.ac;
 
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.StringSelection;
-import java.awt.datatransfer.Transferable;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
 import java.awt.im.InputContext;
 import java.util.Collection;
@@ -14,21 +7,8 @@
 import java.util.LinkedList;
 import java.util.Locale;
-import java.util.Objects;
-import java.util.regex.Pattern;
-
-import javax.swing.JTextField;
-import javax.swing.SwingUtilities;
-import javax.swing.text.AbstractDocument;
-import javax.swing.text.AttributeSet;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.DocumentFilter;
-import javax.swing.text.JTextComponent;
-import javax.swing.text.StyleConstants;
-
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+
+import javax.swing.ComboBoxEditor;
+
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
-import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -45,85 +25,9 @@
  * @since 18173
  */
-public class AutoCompComboBox<E> extends JosmComboBox<E> implements KeyListener {
-
-    /** a regex that matches numbers */
-    private static final Pattern IS_NUMBER = Pattern.compile("^\\d+$");
-    /** true if the combobox should autocomplete */
-    private boolean autocompleteEnabled = true;
-    /** the editor will not accept text longer than this. -1 to disable */
-    private int maxTextLength = -1;
+public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {
+
     /** force a different keyboard input locale for the editor */
     private boolean useFixedLocale;
-
-    /** Whether to autocomplete numbers */
-    private final boolean AUTOCOMPLETE_NUMBERS = !Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true);
-
     private final transient InputContext privateInputContext = InputContext.getInstance();
-
-    static final class InnerFocusListener implements FocusListener {
-        private final JTextComponent editorComponent;
-
-        InnerFocusListener(JTextComponent editorComponent) {
-            this.editorComponent = editorComponent;
-        }
-
-        @Override
-        public void focusLost(FocusEvent e) {
-            MapFrame map = MainApplication.getMap();
-            if (map != null) {
-                map.keyDetector.setEnabled(true);
-            }
-        }
-
-        @Override
-        public void focusGained(FocusEvent e) {
-            MapFrame map = MainApplication.getMap();
-            if (map != null) {
-                map.keyDetector.setEnabled(false);
-            }
-            // save unix system selection (middle mouse paste)
-            Clipboard sysSel = ClipboardUtils.getSystemSelection();
-            if (sysSel != null) {
-                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
-                editorComponent.selectAll();
-                if (old != null) {
-                    sysSel.setContents(old, null);
-                }
-            } else if (e != null && e.getOppositeComponent() != null) {
-                // Select all characters when the change of focus occurs inside JOSM only.
-                // When switching from another application, it is annoying, see #13747
-                editorComponent.selectAll();
-            }
-        }
-    }
-
-    /**
-     * A {@link DocumentFilter} to limit the text length in the editor.
-     */
-    private class MaxLengthDocumentFilter extends DocumentFilter {
-        @Override
-        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
-                throws BadLocationException {
-            if (mustInsertOrReplace(fb, 0, string, attr)) {
-                super.insertString(fb, offset, string, attr);
-            }
-        }
-
-        @Override
-        public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr)
-                throws BadLocationException {
-            if (mustInsertOrReplace(fb, length, string, attr)) {
-                super.replace(fb, offset, length, string, attr);
-            }
-        }
-
-        private boolean mustInsertOrReplace(FilterBypass fb, int length, String string, AttributeSet attr) {
-            int newLen = fb.getDocument().getLength() - length + ((string == null) ? 0 : string.length());
-            return (maxTextLength == -1 || newLen <= maxTextLength ||
-                    // allow longer text while composing characters or it will be hard to compose
-                    // the last characters before the limit
-                    ((attr != null) && attr.isDefined(StyleConstants.ComposedTextAttribute)));
-        }
-    }
 
     /**
@@ -141,10 +45,8 @@
     public AutoCompComboBox(AutoCompComboBoxModel<E> model) {
         super(model);
-        Objects.requireNonNull(model, "A model cannot be null.");
+        setEditor(new AutoCompComboBoxEditor<E>());
         setEditable(true);
-        final JTextComponent editorComponent = getEditorComponent();
-        editorComponent.addFocusListener(new InnerFocusListener(editorComponent));
-        editorComponent.addKeyListener(this);
-        ((AbstractDocument) editorComponent.getDocument()).setDocumentFilter(new MaxLengthDocumentFilter());
+        getEditorComponent().setModel(model);
+        getEditorComponent().addAutoCompListener(this);
     }
 
@@ -152,5 +54,5 @@
      * Returns the {@link AutoCompComboBoxModel} currently used.
      *
-     * @return the model
+     * @return the model or null
      */
     @Override
@@ -159,57 +61,39 @@
     }
 
-    /**
-     * Autocompletes what the user typed in.
-     * <p>
-     * Gets the user input from the editor, finds the best matching item in the model, selects it in
-     * the list, sets the editor text, and highlights the autocompleted part. If there is no
-     * matching item, removes the list selection.
-     */
-    private void autocomplete() {
-        JTextField editor = getEditorComponent();
-        String prefix = editor.getText();
-        if (!AUTOCOMPLETE_NUMBERS && IS_NUMBER.matcher(prefix).matches())
-            return;
-
-        E item = getModel().findBestCandidate(prefix);
-        if (item != null) {
-            String text = item.toString();
-            // This calls setItem() if the selected item changed
-            // See: javax.swing.plaf.basic.BasicComboBoxUI.Handler.contentsChanged(ListDataEvent e)
-            setSelectedItem(item);
-            // set manually in case the selected item didn't change
-            editor.setText(text);
-            // select the autocompleted suffix in the editor
-            editor.select(prefix.length(), text.length());
-            // copy the whole autocompleted string to the unix system-wide selection (aka
-            // middle-click), else only the selected suffix would be copied
-            copyToSysSel(text);
-        } else {
-            setSelectedItem(null);
-            // avoid setItem because it selects the whole text (on windows only)
-            editor.setText(prefix);
-        }
-    }
-
-    /**
-     * Copies a String to the UNIX system-wide selection (aka middle-click).
-     *
-     * @param s the string to copy
-     */
-    void copyToSysSel(String s) {
-        Clipboard sysSel = ClipboardUtils.getSystemSelection();
-        if (sysSel != null) {
-            Transferable transferable = new StringSelection(s);
-            sysSel.setContents(transferable, null);
-        }
-    }
-
-    /**
-     * Sets the maximum text length.
-     *
-     * @param length the maximum text length in number of characters
-     */
-    public void setMaxTextLength(int length) {
-        maxTextLength = length;
+    @Override
+    public void setEditor(ComboBoxEditor newEditor) {
+        if (editor != null) {
+            editor.getEditorComponent().removePropertyChangeListener(this);
+        }
+        super.setEditor(newEditor);
+        if (editor != null) {
+            // listen to orientation changes in the editor
+            editor.getEditorComponent().addPropertyChangeListener(this);
+        }
+    }
+
+    /**
+     * Returns the editor component
+     *
+     * @return the editor component
+     * @see ComboBoxEditor#getEditorComponent()
+     * @since 18221
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public AutoCompTextField<E> getEditorComponent() {
+        return getEditor() == null ? null : (AutoCompTextField<E>) getEditor().getEditorComponent();
+    }
+
+    /**
+     * Selects the autocompleted item in the dropdown.
+     *
+     * @param item the item selected for autocomplete
+     */
+    private void autocomplete(Object item) {
+        // Save the text in case item is null, because setSelectedItem will erase it.
+        String savedText = getText();
+        setSelectedItem(item);
+        setText(savedText);
     }
 
@@ -263,13 +147,4 @@
 
     /**
-     * Returns {@code true} if autocompletion is enabled.
-     *
-     * @return {@code true} if autocompletion is enabled.
-     */
-    public final boolean isAutocompleteEnabled() {
-        return autocompleteEnabled;
-    }
-
-    /**
      * Enables or disables the autocompletion.
      *
@@ -279,7 +154,5 @@
      */
     public boolean setAutocompleteEnabled(boolean enabled) {
-        boolean oldEnabled = this.autocompleteEnabled;
-        this.autocompleteEnabled = enabled;
-        return oldEnabled;
+        return getEditorComponent().setAutocompleteEnabled(enabled);
     }
 
@@ -319,31 +192,13 @@
     }
 
-    /*
-     * The KeyListener interface
-     */
-
-    /**
-     * Listens to key events and eventually schedules an autocomplete.
-     *
-     * @param e the key event
-     */
-    @Override
-    public void keyTyped(KeyEvent e) {
-        if (autocompleteEnabled
-                // and selection is at the end
-                && getEditorComponent().getSelectionEnd() == getEditorComponent().getText().length()
-                // and something visible was typed
-                && !Character.isISOControl(e.getKeyChar())) {
-            // We got the event before the editor component could see it. Let the editor do its job first.
-            SwingUtilities.invokeLater(() -> autocomplete());
-        }
-    }
-
-    @Override
-    public void keyPressed(KeyEvent e) {
-    }
-
-    @Override
-    public void keyReleased(KeyEvent e) {
+    /** AutoCompListener Interface */
+
+    @Override
+    public void autoCompBefore(AutoCompEvent e) {
+    }
+
+    @Override
+    public void autoCompPerformed(AutoCompEvent e) {
+        autocomplete(e.getItem());
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxEditor.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxEditor.java	(revision 18221)
@@ -0,0 +1,28 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxEditor;
+
+/**
+ * A {@link javax.swing.ComboBoxEditor} that uses an {@link AutoCompTextField}.
+ * <p>
+ * This lets us stick an {@code AutoCompTextField} into a {@link javax.swing.JComboBox}.  This is not
+ * used for {@link AutoCompComboBox}.
+ *
+ * @param <E> the type of the items in the editor
+ * @since 18221
+ */
+public class AutoCompComboBoxEditor<E> extends JosmComboBoxEditor {
+
+    @Override
+    protected AutoCompTextField<E> createEditorComponent() {
+        return new AutoCompTextField<>();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public AutoCompTextField<E> getEditorComponent() {
+        // this cast holds unless somebody overrides createEditorComponent()
+        return (AutoCompTextField<E>) editor;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java	(revision 18221)
@@ -2,17 +2,8 @@
 package org.openstreetmap.josm.gui.tagging.ac;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Objects;
-import java.util.function.Function;
 
-import javax.swing.AbstractListModel;
-import javax.swing.MutableComboBoxModel;
-
-import org.openstreetmap.josm.data.preferences.ListProperty;
-import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
 
 /**
@@ -23,5 +14,5 @@
  * @since 18173
  */
-public class AutoCompComboBoxModel<E> extends AbstractListModel<E> implements MutableComboBoxModel<E>, Iterable<E> {
+public class AutoCompComboBoxModel<E> extends JosmComboBoxModel<E> {
 
     /**
@@ -33,11 +24,4 @@
      */
     private Comparator<E> comparator;
-    /** The maximum number of elements to hold, -1 for no limit. Used for histories. */
-    private int maxSize = -1;
-
-    /** the elements shown in the dropdown */
-    protected ArrayList<E> elements = new ArrayList<>();
-    /** the selected element in the dropdown or null */
-    protected Object selected;
 
     /**
@@ -74,175 +58,4 @@
 
     /**
-     * Sets the maximum number of elements.
-     *
-     * @param size The maximal number of elements in the model.
-     */
-    public void setSize(int size) {
-        maxSize = size;
-    }
-
-    /**
-     * Returns a copy of the element list.
-     * @return a copy of the data
-     */
-    public Collection<E> asCollection() {
-        return new ArrayList<>(elements);
-    }
-
-    //
-    // interface java.lang.Iterable
-    //
-
-    @Override
-    public Iterator<E> iterator() {
-        return elements.iterator();
-    }
-
-    //
-    // interface javax.swing.MutableComboBoxModel
-    //
-
-    /**
-     * Adds an element to the end of the model. Does nothing if max size is already reached.
-     */
-    @Override
-    public void addElement(E element) {
-        if (element != null && (maxSize == -1 || getSize() < maxSize)) {
-            elements.add(element);
-        }
-    }
-
-    @Override
-    public void removeElement(Object elem) {
-        elements.remove(elem);
-    }
-
-    @Override
-    public void removeElementAt(int index) {
-        Object elem = getElementAt(index);
-        if (elem == selected) {
-            if (index == 0) {
-                setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1));
-            } else {
-                setSelectedItem(getElementAt(index - 1));
-            }
-        }
-        elements.remove(index);
-        fireIntervalRemoved(this, index, index);
-    }
-
-    /**
-     * Adds an element at a specific index.
-     *
-     * @param element The element to add
-     * @param index Location to add the element
-     */
-    @Override
-    public void insertElementAt(E element, int index) {
-        if (maxSize != -1 && maxSize <= getSize()) {
-            removeElementAt(getSize() - 1);
-        }
-        elements.add(index, element);
-    }
-
-    //
-    // javax.swing.ComboBoxModel
-    //
-
-    /**
-     * Set the value of the selected item. The selected item may be null.
-     *
-     * @param elem The combo box value or null for no selection.
-     */
-    @Override
-    public void setSelectedItem(Object elem) {
-        if ((selected != null && !selected.equals(elem)) ||
-            (selected == null && elem != null)) {
-            selected = elem;
-            fireContentsChanged(this, -1, -1);
-        }
-    }
-
-    @Override
-    public Object getSelectedItem() {
-        return selected;
-    }
-
-    //
-    // javax.swing.ListModel
-    //
-
-    @Override
-    public int getSize() {
-        return elements.size();
-    }
-
-    @Override
-    public E getElementAt(int index) {
-        if (index >= 0 && index < elements.size())
-            return elements.get(index);
-        else
-            return null;
-    }
-
-    //
-    // end interfaces
-    //
-
-    /**
-     * Adds all elements from the collection.
-     *
-     * @param elems The elements to add.
-     */
-    public void addAllElements(Collection<E> elems) {
-        elems.forEach(e -> addElement(e));
-    }
-
-    /**
-     * Adds all elements from the collection of string representations.
-     *
-     * @param strings The string representation of the elements to add.
-     * @param buildE A {@link java.util.function.Function} that builds an {@code <E>} from a
-     *               {@code String}.
-     */
-    public void addAllElements(Collection<String> strings, Function<String, E> buildE) {
-        strings.forEach(s -> addElement(buildE.apply(s)));
-    }
-
-    /**
-     * 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,
-     * deletes the last element.
-     *
-     * @param newElement the element to add
-     * @return The element that is at the top now.
-     */
-    public E addTopElement(E newElement) {
-        // if the element is already at the top, do nothing
-        if (newElement.equals(getElementAt(0)))
-            return getElementAt(0);
-
-        removeElement(newElement);
-        insertElementAt(newElement, 0);
-        return newElement;
-    }
-
-    /**
-     * Empties the list.
-     */
-    public void removeAllElements() {
-        if (!elements.isEmpty()) {
-            int firstIndex = 0;
-            int lastIndex = elements.size() - 1;
-            elements.clear();
-            selected = null;
-            fireIntervalRemoved(this, firstIndex, lastIndex);
-        } else {
-            selected = null;
-        }
-    }
-
-    /**
      * Finds the best candidate for autocompletion.
      * <p>
@@ -263,97 +76,3 @@
             .orElse(null);
     }
-
-    /**
-     * Gets a preference loader and saver.
-     *
-     * @param readE A {@link Function} that builds an {@code <E>} from a {@link String}.
-     * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
-     * @return The {@link Preferences} instance.
-     */
-    public Preferences prefs(Function<String, E> readE, Function<E, String> writeE) {
-        return new Preferences(readE, writeE);
-    }
-
-    /**
-     * Loads and saves the model to the JOSM preferences.
-     * <p>
-     * Obtainable through {@link #prefs}.
-     */
-    public final class Preferences {
-
-        /** A {@link Function} that builds an {@code <E>} from a {@code String}. */
-        private Function<String, E> readE;
-        /** A {@code Function} that serializes {@code <E>} to a {@code String}. */
-        private Function<E, String> writeE;
-
-        /**
-         * Private constructor
-         *
-         * @param readE A {@link Function} that builds an {@code <E>} from a {@code String}.
-         * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
-         */
-        private Preferences(Function<String, E> readE, Function<E, String> writeE) {
-            this.readE = readE;
-            this.writeE = writeE;
-        }
-
-        /**
-         * Loads the model from the JOSM preferences.
-         * @param key The preferences key
-         */
-        public void load(String key) {
-            removeAllElements();
-            addAllElements(Config.getPref().getList(key), readE);
-        }
-
-        /**
-         * Loads the model from the JOSM preferences.
-         *
-         * @param key The preferences key
-         * @param defaults A list of default values.
-         */
-        public void load(String key, List<String> defaults) {
-            removeAllElements();
-            addAllElements(Config.getPref().getList(key, defaults), readE);
-        }
-
-        /**
-         * Loads the model from the JOSM preferences.
-         *
-         * @param prop The property holding the strings.
-         */
-        public void load(ListProperty prop) {
-            removeAllElements();
-            addAllElements(prop.get(), readE);
-        }
-
-        /**
-         * Returns the model elements as list of strings.
-         *
-         * @return a list of strings
-         */
-        public List<String> asStringList() {
-            List<String> list = new ArrayList<>(getSize());
-            forEach(element -> list.add(writeE.apply(element)));
-            return list;
-        }
-
-        /**
-         * Saves the model to the JOSM preferences.
-         *
-        * @param key The preferences key
-        */
-        public void save(String key) {
-            Config.getPref().putList(key, asStringList());
-        }
-
-        /**
-         * Saves the model to the JOSM preferences.
-         *
-         * @param prop The property to write to.
-         */
-        public void save(ListProperty prop) {
-            prop.put(asStringList());
-        }
-    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java	(revision 18221)
@@ -0,0 +1,94 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+import java.awt.AWTEvent;
+
+/**
+ * This event is generated by an AutoCompTextField when an autocomplete occured.
+ *
+ * @see AutoCompTextField
+ * @see AutoCompListener
+ * @since 18221
+ */
+public class AutoCompEvent extends AWTEvent {
+
+    /**
+     * The first number in the range of ids used for autoComp events.
+     */
+    public static final int AUTOCOMP_FIRST = 5900;
+
+    /**
+     * The last number in the range of ids used for autoComp events.
+     */
+    public static final int AUTOCOMP_LAST = 5901;
+
+    /**
+     * This event id indicates that an autocomp is about to start.
+     */
+    public static final int AUTOCOMP_BEFORE = AUTOCOMP_FIRST;
+
+    /**
+     * This event id indicates that an autocomp completed.
+     */
+    public static final int AUTOCOMP_DONE = AUTOCOMP_FIRST + 1;
+
+    /*
+     * JDK 1.1 serialVersionUID
+     */
+    private static final long serialVersionUID = 3745384758753475838L;
+
+    /** the selected autocomplete item */
+    private Object item;
+
+    /**
+     * Constructs a <code>AutoCompEvent</code> object.
+     * <p> This method throws an
+     * <code>IllegalArgumentException</code> if <code>source</code>
+     * is <code>null</code>.
+     *
+     * @param source The (<code>AutoCompTextField</code>) object that
+     *               originated the event
+     * @param id     An integer that identifies the event type.
+     *               For information on allowable values, see
+     *               the class description for {@link AutoCompEvent}
+     * @param item   The item selected for autocompletion.
+     * @throws IllegalArgumentException if <code>source</code> is null
+     * @see #getSource()
+     * @see #getID()
+     */
+    public AutoCompEvent(Object source, int id, Object item) {
+        super(source, id);
+        this.item = item;
+    }
+
+    /**
+     * Returns the item selected for autocompletion.
+     *
+     * @return the item selected for autocompletion
+     */
+    public Object getItem() {
+        return item;
+    }
+
+    /**
+     * Returns a parameter string identifying this text event.
+     * This method is useful for event-logging and for debugging.
+     *
+     * @return a string identifying the event and its attributes
+     */
+    @Override
+    public String paramString() {
+        String typeStr;
+        switch(id) {
+            case AUTOCOMP_BEFORE:
+                typeStr = "AUTOCOMP_BEFORE";
+                break;
+            case AUTOCOMP_DONE:
+                typeStr = "AUTOCOMP_DONE";
+                break;
+          default:
+                typeStr = "unknown type";
+        }
+        return typeStr;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java	(revision 18221)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+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.
+ *
+ * @see AutoCompEvent
+ * @since 18221
+ */
+public interface AutoCompListener extends EventListener {
+
+    /**
+     * Invoked before an autocomplete.  You can use this to change the model.
+     *
+     * @param e an {@link AutoCompEvent}
+     */
+    void autoCompBefore(AutoCompEvent e);
+
+    /**
+     * Invoked after an autocomplete happened.
+     *
+     * @param e an {@link AutoCompEvent}
+     */
+    void autoCompPerformed(AutoCompEvent e);
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java	(revision 18221)
@@ -0,0 +1,339 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+import java.awt.Component;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.EventObject;
+import java.util.regex.Pattern;
+
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.event.CellEditorListener;
+import javax.swing.table.TableCellEditor;
+import javax.swing.text.AbstractDocument;
+
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.util.CellEditorSupport;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.spi.preferences.Config;
+
+/**
+ * An auto-completing TextField.
+ * <p>
+ * When the user starts typing, this textfield will suggest the
+ * {@link AutoCompComboBoxModel#findBestCandidate best matching item} from its model.  The items in
+ * the model can be of any type while the items' {@code toString} values are used for
+ * autocompletion.
+ *
+ * @param <E> the type of items in the model
+ * @since 18221
+ */
+public class AutoCompTextField<E> extends JosmTextField implements TableCellEditor, KeyListener {
+
+    /** true if the combobox should autocomplete */
+    private boolean autocompleteEnabled = true;
+    /** a filter to enforce max. text length */
+    private MaxLengthDocumentFilter docFilter;
+    /** the model */
+    protected AutoCompComboBoxModel<E> model;
+    /** Whether to autocomplete numbers */
+    private final boolean AUTOCOMPLETE_NUMBERS = !Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true);
+    /** a regex that matches numbers */
+    private static final Pattern IS_NUMBER = Pattern.compile("^\\d+$");
+
+    protected final void init() {
+        model = new AutoCompComboBoxModel<>();
+        docFilter = new MaxLengthDocumentFilter();
+        ((AbstractDocument) getDocument()).setDocumentFilter(docFilter);
+        addKeyListener(this);
+        tableCellEditorSupport = new CellEditorSupport(this);
+    }
+
+    /**
+     * Constructs a new {@code AutoCompTextField}.
+     */
+    public AutoCompTextField() {
+        this(0);
+    }
+
+    /**
+     * Constructs a new {@code AutoCompTextField}.
+     * @param model the model to use
+     */
+    public AutoCompTextField(AutoCompComboBoxModel<E> model) {
+        this(0);
+        this.model = model;
+    }
+
+    /**
+     * Constructs a new {@code AutoCompTextField}.
+     * @param columns the number of columns to use to calculate the preferred width;
+     * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation
+     */
+    public AutoCompTextField(int columns) {
+        this(columns, true);
+    }
+
+    /**
+     * Constructs a new {@code AutoCompTextField}.
+     * @param columns the number of columns to use to calculate the preferred width;
+     * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation
+     * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor
+     */
+    public AutoCompTextField(int columns, boolean undoRedo) {
+        super(null, null, columns, undoRedo);
+        init();
+    }
+
+    /**
+     * Returns the {@link AutoCompComboBoxModel} currently used.
+     *
+     * @return the model
+     */
+    public AutoCompComboBoxModel<E> getModel() {
+        return model;
+    }
+
+    /**
+     * Sets the data model that the {@code AutoCompTextField} uses to obtain the list of items.
+     *
+     * @param model the {@link AutoCompComboBoxModel} that provides the list of items used for autocomplete
+     */
+    public void setModel(AutoCompComboBoxModel<E> model) {
+        AutoCompComboBoxModel<E> oldModel = this.model;
+        this.model = model;
+        firePropertyChange("model", oldModel, model);
+    }
+
+    /**
+     * Returns {@code true} if autocompletion is enabled.
+     *
+     * @return {@code true} if autocompletion is enabled.
+     */
+    public final boolean isAutocompleteEnabled() {
+        return autocompleteEnabled;
+    }
+
+    /**
+     * Enables or disables the autocompletion.
+     *
+     * @param enabled {@code true} to enable autocompletion
+     * @return {@code true} if autocomplete was enabled before calling this
+     */
+    public boolean setAutocompleteEnabled(boolean enabled) {
+        boolean oldEnabled = this.autocompleteEnabled;
+        this.autocompleteEnabled = enabled;
+        return oldEnabled;
+    }
+
+    /**
+     * Sets the maximum number of characters allowed.
+     * @param length maximum number of characters allowed
+     */
+    public void setMaxTextLength(int length) {
+        docFilter.setMaxLength(length);
+    }
+
+    /**
+     * Autocompletes what the user typed in.
+     * <p>
+     * Gets the user input from the editor, finds the best matching item in the model, sets the
+     * editor text to it, and highlights the autocompleted part. If there is no matching item, removes the
+     * list selection.
+     *
+     * @param oldText the text before the last keypress was processed
+     */
+    private void autocomplete(String oldText) {
+        String newText = getText();
+        if (getSelectionEnd() != newText.length())
+            // selection not at the end
+            return;
+        // if the user typed some control character (eg. Alt+A) the selection may still be there
+        String unSelected = newText.substring(0, getSelectionStart());
+        if (unSelected.length() <= oldText.length())
+            // do not autocomplete on control or deleted chars
+            return;
+        if (!AUTOCOMPLETE_NUMBERS && IS_NUMBER.matcher(newText).matches())
+            return;
+
+        fireAutoCompEvent(AutoCompEvent.AUTOCOMP_BEFORE, null);
+        E item = getModel().findBestCandidate(newText);
+        fireAutoCompEvent(AutoCompEvent.AUTOCOMP_DONE, item);
+
+        if (item != null) {
+            String text = item.toString();
+            setText(text);
+            // select the autocompleted suffix in the editor
+            select(newText.length(), text.length());
+            // copy the whole autocompleted string to the unix system-wide selection (aka
+            // middle-click), else only the selected suffix would be copied
+            copyToSysSel(text);
+        }
+    }
+
+    /**
+     * Copies a String to the UNIX system-wide selection (aka middle-click).
+     *
+     * @param s the string to copy
+     */
+    void copyToSysSel(String s) {
+        Clipboard sysSel = ClipboardUtils.getSystemSelection();
+        if (sysSel != null) {
+            Transferable transferable = new StringSelection(s);
+            sysSel.setContents(transferable, null);
+        }
+    }
+
+    /**
+     * Adds an AutoCompListener.
+     *
+     * @param l the AutoComp listener to be added
+     */
+    public synchronized void addAutoCompListener(AutoCompListener l) {
+        listenerList.add(AutoCompListener.class, l);
+    }
+
+    /**
+     * Removes the specified AutoCompListener.
+     *
+     * @param l the autoComp listener to be removed
+     */
+    public synchronized void removeActionListener(AutoCompListener l) {
+        if ((l != null) && (getAction() == l)) {
+            setAction(null);
+        } else {
+            listenerList.remove(AutoCompListener.class, l);
+        }
+    }
+
+    /**
+     * Returns an array of all the current <code>AutoCompListener</code>s.
+     *
+     * @return all of the <code>AutoCompListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    public synchronized AutoCompListener[] getAutoCompListeners() {
+        return listenerList.getListeners(AutoCompListener.class);
+    }
+
+    /**
+     * Notifies all listeners that have registered interest for notification on this event type.
+     * The event instance is lazily created. The listener list is processed in last to first order.
+     *
+     * @param id The Autocomp event id
+     * @param item The item selected for autocompletion.
+     * @see javax.swing.event.EventListenerList
+     */
+    protected void fireAutoCompEvent(int id, Object item) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        AutoCompEvent e = new AutoCompEvent(this, id, item);
+
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == AutoCompListener.class) {
+                switch (id) {
+                    case AutoCompEvent.AUTOCOMP_DONE:
+                        ((AutoCompListener) listeners[i + 1]).autoCompPerformed(e);
+                        break;
+                    case AutoCompEvent.AUTOCOMP_BEFORE:
+                        ((AutoCompListener) listeners[i + 1]).autoCompBefore(e);
+                        break;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------------------------------ */
+    /* KeyListener interface                                                                */
+    /* ------------------------------------------------------------------------------------ */
+
+    /**
+     * Listens to key events and eventually schedules an autocomplete.
+     *
+     * @param e the key event
+     */
+    @Override
+    public void keyTyped(KeyEvent e) {
+        if (autocompleteEnabled) {
+            // if selection is at the end
+            if (getSelectionEnd() == getText().length()) {
+                final String oldText = getText().substring(0, getSelectionStart());
+                // We got the event before the editor component could see it. Let the editor do its job first.
+                SwingUtilities.invokeLater(() -> autocomplete(oldText));
+            }
+        }
+    }
+
+    @Override
+    public void keyPressed(KeyEvent e) {
+    }
+
+    @Override
+    public void keyReleased(KeyEvent e) {
+    }
+
+    /* ------------------------------------------------------------------------------------ */
+    /* 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: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 18221)
@@ -346,4 +346,28 @@
 
     /**
+     * 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
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/MaxLengthDocumentFilter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/MaxLengthDocumentFilter.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/MaxLengthDocumentFilter.java	(revision 18221)
@@ -0,0 +1,49 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.ac;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DocumentFilter;
+import javax.swing.text.StyleConstants;
+
+/**
+ * A {@link DocumentFilter} to limit the text length in the editor.
+ * @since 18221
+ */
+public class MaxLengthDocumentFilter extends DocumentFilter {
+    /** the document will not accept text longer than this. -1 to disable */
+    private int maxLength = -1;
+
+    /**
+     * Sets the maximum text length.
+     *
+     * @param length the maximum no. of charactes allowed in this document. -1 to disable
+     */
+    public void setMaxLength(int length) {
+        maxLength = length;
+    }
+
+    @Override
+    public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
+            throws BadLocationException {
+        if (mustInsertOrReplace(fb, 0, string, attr)) {
+            super.insertString(fb, offset, string, attr);
+        }
+    }
+
+    @Override
+    public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr)
+            throws BadLocationException {
+        if (mustInsertOrReplace(fb, length, string, attr)) {
+            super.replace(fb, offset, length, string, attr);
+        }
+    }
+
+    private boolean mustInsertOrReplace(FilterBypass fb, int length, String string, AttributeSet attr) {
+        int newLen = fb.getDocument().getLength() - length + ((string == null) ? 0 : string.length());
+        return (maxLength == -1 || newLen <= maxLength ||
+                // allow longer text while composing characters or it will be hard to compose
+                // the last characters before the limit
+                ((attr != null) && attr.isDefined(StyleConstants.ComposedTextAttribute)));
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 18221)
@@ -7,4 +7,5 @@
 
 import java.awt.Component;
+import java.awt.ComponentOrientation;
 import java.awt.Dimension;
 import java.awt.GridBagLayout;
@@ -16,5 +17,4 @@
 import java.util.EnumSet;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -91,8 +91,12 @@
 public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive> {
 
+    /** The user pressed the "Apply" button */
     public static final int DIALOG_ANSWER_APPLY = 1;
+    /** The user pressed the "New Relation" button */
     public static final int DIALOG_ANSWER_NEW_RELATION = 2;
+    /** The user pressed the "Cancel" button */
     public static final int DIALOG_ANSWER_CANCEL = 3;
 
+    /** The action key for optional tooltips */
     public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
 
@@ -120,4 +124,7 @@
      */
     public String iconName;
+    /**
+     * Translation context for name
+     */
     public String name_context;
     /**
@@ -126,4 +133,7 @@
      */
     public String locale_name;
+    /**
+     * Show the preset name if true
+     */
     public boolean preset_name_label;
 
@@ -132,8 +142,21 @@
      */
     public transient Set<TaggingPresetType> types;
+    /**
+     * The list of preset items
+     */
     public final transient List<TaggingPresetItem> data = new ArrayList<>(2);
+    /**
+     * The roles for this relation (if we are editing a relation). See:
+     * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Tags">JOSM wiki</a>
+     */
     public transient Roles roles;
+    /**
+     * The name_template custom name formatter. See:
+     * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a>
+     */
     public transient TemplateEntry nameTemplate;
+    /** The name_template_filter */
     public transient Match nameTemplateFilter;
+    /** The match_expression */
     public transient Match matchExpression;
 
@@ -145,4 +168,7 @@
     /** The completable future task of asynchronous icon loading */
     private CompletableFuture<Void> iconFuture;
+
+    /** Support functions */
+    protected TaggingPresetItemGuiSupport itemGuiSupport;
 
     /**
@@ -277,13 +303,27 @@
     }
 
-    public void setName_template(String pattern) throws SAXException {
+    /**
+     * Sets the name_template custom name formatter.
+     *
+     * @param template The format template
+     * @throws SAXException on template parse error
+     * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a>
+     */
+    public void setName_template(String template) throws SAXException {
         try {
-            this.nameTemplate = new TemplateParser(pattern).parse();
+            this.nameTemplate = new TemplateParser(template).parse();
         } catch (ParseError e) {
-            Logging.error("Error while parsing " + pattern + ": " + e.getMessage());
+            Logging.error("Error while parsing " + template + ": " + e.getMessage());
             throw new SAXException(e);
         }
     }
 
+    /**
+     * Sets the name_template_filter.
+     *
+     * @param filter The search pattern
+     * @throws SAXException on search patern parse error
+     * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a>
+     */
     public void setName_template_filter(String filter) throws SAXException {
         try {
@@ -295,4 +335,11 @@
     }
 
+    /**
+     * Sets the match_expression additional criteria for matching primitives.
+     *
+     * @param filter The search pattern
+     * @throws SAXException on search patern parse error
+     * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a>
+     */
     public void setMatch_expression(String filter) throws SAXException {
         try {
@@ -321,5 +368,4 @@
     public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
         PresetPanel p = new PresetPanel();
-        List<Link> l = new LinkedList<>();
 
         final JPanel pp = new JPanel();
@@ -355,11 +401,28 @@
 
         boolean presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(this);
-        final TaggingPresetItemGuiSupport itemGuiSupport = TaggingPresetItemGuiSupport.create(
-                presetInitiallyMatches, selected, this::getChangedTags);
-        JPanel items = new JPanel(new GridBagLayout());
+        itemGuiSupport = TaggingPresetItemGuiSupport.create(presetInitiallyMatches, selected, this::getChangedTags);
+
+        JPanel itemPanel = new JPanel(new GridBagLayout()) {
+            /**
+             * This hack allows the items to have their own orientation.
+             *
+             * The problem is that
+             * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls
+             * {@code applyComponentOrientation} very late in the dialog construction process thus
+             * overwriting the orientation the components have chosen for themselves.
+             *
+             * This stops the propagation of {@code applyComponentOrientation}, thus all
+             * {@code TaggingPresetItem}s may (and have to) set their own orientation.
+             */
+            @Override
+            public void applyComponentOrientation(ComponentOrientation o) {
+                setComponentOrientation(o);
+            }
+        };
+        JPanel linkPanel = new JPanel(new GridBagLayout());
         TaggingPresetItem previous = null;
         for (TaggingPresetItem i : data) {
             if (i instanceof Link) {
-                l.add((Link) i);
+                i.addToPanel(linkPanel, itemGuiSupport);
                 p.hasElements = true;
             } else {
@@ -367,8 +430,8 @@
                     PresetLink link = (PresetLink) i;
                     if (!(previous instanceof PresetLink && Objects.equals(((PresetLink) previous).text, link.text))) {
-                        items.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0));
+                        itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0));
                     }
                 }
-                if (i.addToPanel(items, itemGuiSupport)) {
+                if (i.addToPanel(itemPanel, itemGuiSupport)) {
                     p.hasElements = true;
                 }
@@ -376,7 +439,9 @@
             previous = i;
         }
-        p.add(items, GBC.eol().fill());
+        p.add(itemPanel, GBC.eol().fill());
+        p.add(linkPanel, GBC.eol().fill());
+
         if (selected.isEmpty() && !supportsRelation()) {
-            GuiHelper.setEnabledRec(items, false);
+            GuiHelper.setEnabledRec(itemPanel, false);
         }
 
@@ -384,9 +449,4 @@
             itemGuiSupport.addListener((source, key, newValue) ->
                     TaggingPresetValidation.validateAsync(selected.iterator().next(), validationLabel, getChangedTags()));
-        }
-
-        // add Link
-        for (Link link : l) {
-            link.addToPanel(p, itemGuiSupport);
         }
 
@@ -396,6 +456,8 @@
         p.add(tb, GBC.std(1, 0).anchor(GBC.LINE_END));
 
-        // Trigger initial updates
+        // Trigger initial updates once and only once
+        itemGuiSupport.setEnabled(true);
         itemGuiSupport.fireItemValueModified(null, null, null);
+
         return p;
     }
@@ -410,4 +472,10 @@
     }
 
+    /**
+     * Suggests a relation role for this primitive
+     *
+     * @param osm The primitive
+     * @return the suggested role or null
+     */
     public String suggestRoleForOsmPrimitive(OsmPrimitive osm) {
         if (roles != null && osm != null) {
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 18221)
@@ -8,4 +8,5 @@
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
@@ -21,4 +22,5 @@
 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;
@@ -55,4 +57,19 @@
         AutoCompletionManager.of(data).populateWithTagValues(list, keys);
         field.setAutoCompletionList(list);
+    }
+
+    /**
+     * 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
+     */
+    protected List<AutoCompletionItem> getAllForKeys(List<String> keys) {
+        DataSet data = OsmDataManager.getInstance().getEditDataSet();
+        if (data == null) {
+            return Collections.emptyList();
+        }
+        return AutoCompletionManager.of(data).getAllForKeys(keys);
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java	(revision 18221)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm.gui.tagging.presets;
 
+import java.awt.ComponentOrientation;
 import java.util.Arrays;
 import java.util.Collection;
@@ -11,4 +12,5 @@
 import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.data.osm.search.SearchCompiler;
+import org.openstreetmap.josm.gui.widgets.OrientationAction;
 import org.openstreetmap.josm.tools.ListenerList;
 import org.openstreetmap.josm.tools.Utils;
@@ -26,4 +28,28 @@
     private final Supplier<Collection<Tag>> changedTagsSupplier;
     private final ListenerList<ChangeListener> listeners = ListenerList.create();
+
+    /** whether to fire events or not */
+    private boolean enabled = false;
+
+    /**
+     * Returns whether firing of events is enabled
+     *
+     * @return true if firing of events is enabled
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Enables or disables the firing of events
+     *
+     * @param enabled fires if true
+     * @return the old state of enabled
+     */
+    public boolean setEnabled(boolean enabled) {
+        boolean oldEnabled = this.enabled;
+        this.enabled = enabled;
+        return oldEnabled;
+    }
 
     /**
@@ -120,4 +146,13 @@
     }
 
+    /**
+     * Returns the default component orientation by the user's locale
+     *
+     * @return the default component orientation
+     */
+    public ComponentOrientation getDefaultComponentOrientation() {
+        return OrientationAction.getDefaultComponentOrientation();
+    }
+
     @Override
     public boolean evaluateCondition(SearchCompiler.Match condition) {
@@ -140,5 +175,6 @@
      */
     public void fireItemValueModified(TaggingPresetItem source, String key, String newValue) {
-        listeners.fireEvent(e -> e.itemValueModified(source, key, newValue));
+        if (enabled)
+            listeners.fireEvent(e -> e.itemValueModified(source, key, newValue));
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(revision 18221)
@@ -86,6 +86,8 @@
         if (icon != null) {
             JPanel checkPanel = IconTextCheckBox.wrap(check, locale_text, getIcon());
+            checkPanel.applyComponentOrientation(support.getDefaultComponentOrientation());
             p.add(checkPanel, GBC.eol()); // Do not fill, see #15104
         } else {
+            check.applyComponentOrientation(support.getDefaultComponentOrientation());
             p.add(check, GBC.eol()); // Do not fill, see #15104
         }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java	(revision 18221)
@@ -46,4 +46,5 @@
         }
 
+        panel.applyComponentOrientation(support.getDefaultComponentOrientation());
         p.add(panel, GBC.eol());
         return false;
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(revision 18221)
@@ -6,17 +6,25 @@
 import java.awt.Color;
 import java.awt.Cursor;
+import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.Arrays;
+import java.util.Comparator;
 
 import javax.swing.AbstractAction;
 import javax.swing.JButton;
 import javax.swing.JColorChooser;
+import javax.swing.JComponent;
 import javax.swing.JPanel;
 
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
@@ -36,7 +44,27 @@
     public boolean editable = true; // NOSONAR
     /** The length of the combo box (number of characters allowed). */
-    public short length; // NOSONAR
+    public int length; // NOSONAR
 
     protected JosmComboBox<PresetListEntry> combobox;
+    protected AutoCompComboBoxModel<PresetListEntry> dropDownModel;
+    protected AutoCompComboBoxModel<AutoCompletionItem> autoCompModel;
+
+    class ComponentListener extends ComponentAdapter {
+        @Override
+        public void componentResized(ComponentEvent e) {
+            // Make multi-line JLabels the correct size
+            // Only needed if there is any short_description
+            JComponent component = (JComponent) e.getSource();
+            int width = component.getWidth();
+            if (width == 0)
+                width = 200;
+            Insets insets = component.getInsets();
+            width -= insets.left + insets.right + 10;
+            ComboMultiSelectListCellRenderer renderer = (ComboMultiSelectListCellRenderer) combobox.getRenderer();
+            renderer.setWidth(width);
+            combobox.setRenderer(null); // needed to make prop change fire
+            combobox.setRenderer(renderer);
+        }
+    }
 
     /**
@@ -48,5 +76,5 @@
 
     @Override
-    protected void addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {
+    protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {
         if (!usage.unused()) {
             for (String s : usage.values) {
@@ -59,24 +87,36 @@
         presetListEntries.add(new PresetListEntry(""));
 
-        combobox = new JosmComboBox<>(presetListEntries.toArray(new PresetListEntry[0]));
-        component = combobox;
-        combobox.setRenderer(getListCellRenderer());
-        combobox.setEditable(true); // fix incorrect height, see #6157
-        combobox.reinitialize(presetListEntries);
-        combobox.setEditable(editable); // see #6157
-        AutoCompletingTextField tf = new AutoCompletingTextField();
-        initAutoCompletionField(tf, key);
+        dropDownModel = new AutoCompComboBoxModel<PresetListEntry>(Comparator.naturalOrder());
+        autoCompModel = new AutoCompComboBoxModel<AutoCompletionItem>(Comparator.naturalOrder());
+        presetListEntries.forEach(dropDownModel::addElement);
+
+        combobox = new JosmComboBox<>(dropDownModel);
+        AutoCompComboBoxEditor<AutoCompletionItem> editor = new AutoCompComboBoxEditor<>();
+        combobox.setEditor(editor);
+
+        // The default behaviour of JComboBox is to size the editor according to the tallest item in
+        // the dropdown list.  We don't want that to happen because we want to show taller items in
+        // the list than in the editor.  We can't use
+        // {@code combobox.setPrototypeDisplayValue(new PresetListEntry(" "));} because that would
+        // set a fixed cell height in JList.
+        combobox.setPreferredHeight(combobox.getPreferredSize().height);
+
+        // a custom cell renderer capable of displaying a short description text along with the
+        // value
+        combobox.setRenderer(new ComboMultiSelectListCellRenderer(combobox, combobox.getRenderer(), 200, key));
+        combobox.setEditable(editable);
+
+        getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement);
+        getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD)));
+
+        AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent();
+        tf.setModel(autoCompModel);
+
         if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) {
-            tf.setHint(key);
+            combobox.setHint(key);
         }
         if (length > 0) {
-            tf.setMaxChars((int) length);
+            tf.setMaxTextLength(length);
         }
-        AutoCompletionList acList = tf.getAutoCompletionList();
-        if (acList != null) {
-            acList.add(getDisplayValues(), AutoCompletionPriority.IS_IN_STANDARD);
-        }
-        combobox.setEditor(tf);
-        combobox.setSelectedItem(getItemToSelect(def, support, false));
 
         if (key != null && ("colour".equals(key) || key.startsWith("colour:") || key.endsWith(":colour"))) {
@@ -93,5 +133,10 @@
             p.add(combobox, GBC.eol().fill(GBC.HORIZONTAL));
         }
+
+        Object itemToSelect = getItemToSelect(default_, support, false);
+        combobox.setSelectedItemText(itemToSelect == null ? null : itemToSelect.toString());
         combobox.addActionListener(l -> support.fireItemValueModified(this, key, getSelectedValue()));
+        combobox.addComponentListener(new ComponentListener());
+        return combobox;
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(revision 18221)
@@ -6,6 +6,4 @@
 
 import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Font;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -14,5 +12,4 @@
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
@@ -20,5 +17,4 @@
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 
 import javax.swing.JComponent;
@@ -32,4 +28,6 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
+import org.openstreetmap.josm.gui.widgets.OrientationAction;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Logging;
@@ -40,6 +38,4 @@
  */
 public abstract class ComboMultiSelect extends KeyedItem {
-
-    private static final Renderer RENDERER = new Renderer();
 
     /**
@@ -93,5 +89,4 @@
     public boolean values_searchable; // NOSONAR
 
-    protected JComponent component;
     protected final Set<PresetListEntry> presetListEntries = new CopyOnWriteArraySet<>();
     private boolean initialized;
@@ -99,54 +94,51 @@
     protected Object originalValue;
 
-    private static final class Renderer implements ListCellRenderer<PresetListEntry> {
-
-        private final JLabel lbl = new JLabel();
+    /**
+     * A list cell renderer that paints a short text in the current value pane and and a longer text
+     * in the dropdown list.
+     */
+    static class ComboMultiSelectListCellRenderer extends JosmListCellRenderer<PresetListEntry> {
+        int width;
+        private String key;
+
+        ComboMultiSelectListCellRenderer(Component component, ListCellRenderer<? super PresetListEntry> renderer, int width, String key) {
+            super(component, renderer);
+            this.key = key;
+            setWidth(width);
+        }
+
+        /**
+         * Sets the width to format the dropdown list to
+         *
+         * Note: This is not the width of the list, but the width to which we format any multi-line
+         * label in the list.  We cannot use the list's width because at the time the combobox
+         * measures its items, it is not guaranteed that the list is already sized, the combobox may
+         * not even be layed out yet.  Set this to {@code combobox.getWidth()}
+         *
+         * @param width the width
+         */
+        public void setWidth(int width) {
+            if (width <= 0)
+                width = 200;
+            this.width = width - 20;
+        }
 
         @Override
-        public Component getListCellRendererComponent(JList<? extends PresetListEntry> list, PresetListEntry item, int index,
-                boolean isSelected, boolean cellHasFocus) {
-
-            if (list == null || item == null) {
-                return lbl;
-            }
-
-            if (index == -1) {
-                // Take the longest element for the preferred width (#19321)
-                // We do not want the editor to have the maximum height of all entries. Return a dummy with bogus height.
-                IntStream.range(0, list.getModel().getSize())
-                        .mapToObj(i -> getListCellRendererComponent(list, list.getModel().getElementAt(i), i, isSelected, cellHasFocus))
-                        .map(Component::getPreferredSize)
-                        .max(Comparator.comparingInt(dim -> dim.width))
-                        .ifPresent(dim -> lbl.setPreferredSize(new Dimension(dim.width, 10)));
-                return lbl;
-            }
-
-            // Only return cached size, item is not shown
-            if (!list.isShowing() && item.preferredWidth != -1 && item.preferredHeight != -1) {
-                lbl.setPreferredSize(new Dimension(item.preferredWidth, item.preferredHeight));
-                return lbl;
-            }
-
-            lbl.setPreferredSize(null);
-
-            if (isSelected) {
-                lbl.setBackground(list.getSelectionBackground());
-                lbl.setForeground(list.getSelectionForeground());
+        public JLabel getListCellRendererComponent(
+            JList<? extends PresetListEntry> list, PresetListEntry value, int index, boolean isSelected, boolean cellHasFocus) {
+
+            JLabel l = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+            if (index != -1) {
+                // index -1 is set when measuring the size of the cell and when painting the
+                // editor-ersatz of a readonly combobox. fixes #6157
+                l.setText(value.getListDisplay(width));
+            }
+            String tt = value.value;
+            if (tt != null && !tt.isEmpty()) {
+                l.setToolTipText(tr("Sets the key ''{0}'' to the value ''{1}''.", key, tt));
             } else {
-                lbl.setBackground(list.getBackground());
-                lbl.setForeground(list.getForeground());
-            }
-
-            lbl.setOpaque(true);
-            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
-            lbl.setText("<html>" + item.getListDisplay() + "</html>");
-            lbl.setIcon(item.getIcon());
-            lbl.setEnabled(list.isEnabled());
-
-            // Cache size
-            item.preferredWidth = (short) lbl.getPreferredSize().width;
-            item.preferredHeight = (short) lbl.getPreferredSize().height;
-
-            return lbl;
+                l.setToolTipText(tr("Clears the key ''{0}''.", key));
+            }
+            return l;
         }
     }
@@ -188,5 +180,5 @@
     protected abstract Object getSelectedItem();
 
-    protected abstract void addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support);
+    protected abstract JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support);
 
     @Override
@@ -219,8 +211,10 @@
         label.setToolTipText(getKeyTooltipText());
         label.setComponentPopupMenu(getPopupMenu());
+        label.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation());
         p.add(label, GBC.std().insets(0, 0, 10, 0));
-        addToPanelAnchor(p, default_, support);
+        JComponent component = addToPanelAnchor(p, default_, support);
         label.setLabelFor(component);
         component.setToolTipText(getKeyTooltipText());
+        component.applyComponentOrientation(OrientationAction.getValueOrientation(key));
 
         return true;
@@ -455,8 +449,4 @@
     }
 
-    protected ListCellRenderer<PresetListEntry> getListCellRenderer() {
-        return RENDERER;
-    }
-
     @Override
     public MatchType getDefaultMatch() {
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java	(revision 18221)
@@ -18,4 +18,5 @@
         JLabel label = new JLabel(locale_text);
         addIcon(label);
+        label.applyComponentOrientation(support.getDefaultComponentOrientation());
         p.add(label, GBC.eol().fill(GBC.HORIZONTAL));
         return true;
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java	(revision 18221)
@@ -36,5 +36,9 @@
     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
         initializeLocaleText(tr("More information about this feature"));
-        Optional.ofNullable(buildUrlLabel()).ifPresent(label -> p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL)));
+        UrlLabel label = buildUrlLabel();
+        if (label != null) {
+            label.applyComponentOrientation(support.getDefaultComponentOrientation());
+            p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL));
+        }
         return false;
     }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(revision 18221)
@@ -8,8 +8,8 @@
 import java.util.TreeSet;
 
+import javax.swing.JComponent;
 import javax.swing.JList;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
-import javax.swing.ListCellRenderer;
 import javax.swing.ListModel;
 
@@ -31,10 +31,10 @@
 
     @Override
-    protected void addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {
+    protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {
         list = new ConcatenatingJList(delimiter, presetListEntries.toArray(new PresetListEntry[0]));
-        component = list;
-        ListCellRenderer<PresetListEntry> renderer = getListCellRenderer();
+        ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key);
         list.setCellRenderer(renderer);
-        list.setSelectedItem(getItemToSelect(def, support, true));
+        Object itemToSelect = getItemToSelect(def, support, true);
+        list.setSelectedItem(itemToSelect == null ? null : new PresetListEntry(itemToSelect.toString()));
         JScrollPane sp = new JScrollPane(list);
         // if a number of rows has been specified in the preset,
@@ -47,4 +47,5 @@
         list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedValue()));
         p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));
+        return list;
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java	(revision 18221)
@@ -19,7 +19,9 @@
     @Override
     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
+        JLabel label = new JLabel(locale_text);
+        label.applyComponentOrientation(support.getDefaultComponentOrientation());
         initializeLocaleText(tr("Optional Attributes:"));
         p.add(new JLabel(" "), GBC.eol()); // space
-        p.add(new JLabel(locale_text), GBC.eol());
+        p.add(label, GBC.eol());
         p.add(new JLabel(" "), GBC.eol()); // space
         return false;
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java	(revision 18221)
@@ -62,4 +62,5 @@
             JLabel lbl = new TaggingPresetLabel(t);
             lbl.addMouseListener(new TaggingPresetMouseAdapter(t, support.getSelected()));
+            lbl.applyComponentOrientation(support.getDefaultComponentOrientation());
             p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));
         }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java	(revision 18221)
@@ -36,9 +36,4 @@
     public String locale_short_description; // NOSONAR
 
-    /** Cached width (currently only for Combo) to speed up preset dialog initialization */
-    public short preferredWidth = -1; // NOSONAR
-    /** Cached height (currently only for Combo) to speed up preset dialog initialization */
-    public short preferredHeight = -1; // NOSONAR
-
     /**
      * Constructs a new {@code PresetListEntry}, uninitialized.
@@ -57,25 +52,32 @@
 
     /**
-     * Returns HTML formatted contents.
+     * Returns the contents displayed in the dropdown list.
+     *
+     * This is the contents that would be displayed in the current view plus a short description to
+     * aid the user.  The whole contents is wrapped to {@code width}.
+     *
+     * @param width the width in px
      * @return HTML formatted contents
      */
-    public String getListDisplay() {
-        if (value.equals(KeyedItem.DIFFERENT))
-            return "<b>" + Utils.escapeReservedCharactersHTML(KeyedItem.DIFFERENT) + "</b>";
+    public String getListDisplay(int width) {
+        if (value.equals(KeyedItem.DIFFERENT)) {
+            return "<b>" + KeyedItem.DIFFERENT + "</b>";
+        }
 
-        String displayValue = Utils.escapeReservedCharactersHTML(getDisplayValue());
         String shortDescription = getShortDescription(true);
+        String displayValue = getDisplayValue();
 
-        if (displayValue.isEmpty() && Utils.isEmpty(shortDescription))
-            return "&nbsp;";
+        if (shortDescription.isEmpty()) {
+            if (displayValue.isEmpty()) {
+                return " ";
+            }
+            return displayValue;
+        }
 
-        final StringBuilder res = new StringBuilder("<b>").append(displayValue).append("</b>");
-        if (!Utils.isEmpty(shortDescription)) {
-            // wrap in table to restrict the text width
-            res.append("<div style=\"width:300px; padding:0 0 5px 5px\">")
-               .append(shortDescription)
-               .append("</div>");
-        }
-        return res.toString();
+        // RTL not supported in HTML. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4866977
+        return String.format("<html><div style=\"width: %d\"><b>%s</b><p style=\"padding-left: 10\">%s</p></div></html>",
+                width,
+                displayValue,
+                Utils.escapeReservedCharactersHTML(shortDescription));
     }
 
@@ -89,5 +91,5 @@
 
     /**
-     * Returns the value to display.
+     * Returns the contents of the current item view.
      * @return the value to display
      */
@@ -102,7 +104,8 @@
      */
     public String getShortDescription(boolean translated) {
-        return translated
+        String shortDesc = translated
                 ? Utils.firstNonNull(locale_short_description, tr(short_description))
                         : short_description;
+        return shortDesc == null ? "" : shortDesc;
     }
 
@@ -113,5 +116,5 @@
             return KeyedItem.DIFFERENT;
         String displayValue = getDisplayValue();
-        return displayValue != null ? displayValue.replaceAll("<.*>", "") : ""; // remove additional markup, e.g. <br>
+        return displayValue != null ? displayValue.replaceAll("\\s*<.*>\\s*", " ") : ""; // remove additional markup, e.g. <br>
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 18221)
@@ -201,4 +201,5 @@
                 i.addToPanel(proles);
             }
+            proles.applyComponentOrientation(support.getDefaultComponentOrientation());
             p.add(proles, GBC.eol());
         }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(revision 18221)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.util.ArrayList;
 import java.awt.Color;
 import java.awt.Component;
@@ -24,5 +25,8 @@
 
 import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
@@ -31,4 +35,5 @@
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.gui.widgets.OrientationAction;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Logging;
@@ -70,17 +75,34 @@
     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
 
+        AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>();
+        List<String> keys = new ArrayList<>();
+        keys.add(key);
+        if (alternative_autocomplete_keys != null) {
+            for (String k : alternative_autocomplete_keys.split(",", -1)) {
+                keys.add(k);
+            }
+        }
+        getAllForKeys(keys).forEach(model::addElement);
+
+        AutoCompTextField<AutoCompletionItem> textField;
+        AutoCompComboBoxEditor<AutoCompletionItem> editor = null;
+
         // find out if our key is already used in the selection.
         Usage usage = determineTextUsage(support.getSelected(), key);
-        AutoCompletingTextField textField = new AutoCompletingTextField();
-        if (alternative_autocomplete_keys != null) {
-            initAutoCompletionField(textField, (key + ',' + alternative_autocomplete_keys).split(",", -1));
+
+        if (usage.unused() || usage.hasUniqueValue()) {
+            textField = new AutoCompTextField<>();
         } else {
-            initAutoCompletionField(textField, key);
+            editor = new AutoCompComboBoxEditor<>();
+            textField = editor.getEditorComponent();
+        }
+        textField.setModel(model);
+        value = textField;
+
+        if (length > 0) {
+            textField.setMaxTextLength(length);
         }
         if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) {
             textField.setHint(key);
-        }
-        if (length > 0) {
-            textField.setMaxChars((int) length);
         }
         if (usage.unused()) {
@@ -111,9 +133,12 @@
             value = textField;
             originalValue = usage.getFirst();
-        } else {
-            // the objects have different values
+        }
+        if (editor != null) {
+            // The selected primitives have different values for this key.   <b>Note:</b> this
+            // cannot be an AutoCompComboBox because the values in the dropdown are different from
+            // those we autocomplete on.
             JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0]));
             comboBox.setEditable(true);
-            comboBox.setEditor(textField);
+            comboBox.setEditor(editor);
             comboBox.getEditor().setItem(DIFFERENT);
             value = comboBox;
@@ -180,5 +205,7 @@
         p.add(label, GBC.std().insets(0, 0, 10, 0));
         p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
+        label.applyComponentOrientation(support.getDefaultComponentOrientation());
         value.setToolTipText(getKeyTooltipText());
+        value.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key));
         return true;
     }
@@ -253,5 +280,5 @@
     }
 
-    private void setupListeners(AutoCompletingTextField textField, TaggingPresetItemGuiSupport support) {
+    private void setupListeners(AutoCompTextField<AutoCompletionItem> textField, TaggingPresetItemGuiSupport support) {
         // value_templates don't work well with multiple selected items because,
         // as the command queue is currently implemented, we can only save
@@ -266,5 +293,5 @@
                 Logging.trace("Evaluating value_template {0} for key {1} from {2} with new value {3} => {4}",
                         valueTemplate, key, source, newValue, valueTemplateText);
-                textField.setItem(valueTemplateText);
+                textField.setText(valueTemplateText);
                 if (originalValue != null && !originalValue.equals(valueTemplateText)) {
                     textField.setForeground(Color.RED);
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java	(revision 18221)
@@ -5,4 +5,5 @@
 
 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
@@ -14,5 +15,5 @@
  * @since 5765
  */
-public abstract class AbstractIdTextField<T extends AbstractTextComponentValidator> extends JosmTextField {
+public abstract class AbstractIdTextField<T extends AbstractTextComponentValidator> extends AutoCompTextField<String> {
 
     protected final transient T validator;
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java	(revision 18221)
@@ -2,53 +2,75 @@
 package org.openstreetmap.josm.gui.widgets;
 
+
 import java.awt.Component;
+import java.awt.ComponentOrientation;
 import java.awt.Dimension;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
+import java.awt.Graphics;
+import java.awt.GraphicsConfiguration;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 
 import javax.swing.ComboBoxEditor;
-import javax.swing.ComboBoxModel;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.JComboBox;
 import javax.swing.JList;
+import javax.swing.JScrollPane;
 import javax.swing.JTextField;
-import javax.swing.plaf.basic.ComboPopup;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.Border;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 import javax.swing.text.JTextComponent;
 
-import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.spi.preferences.Config;
 
 /**
- * Class overriding each {@link JComboBox} in JOSM to control consistently the number of displayed items at once.<br>
- * This is needed because of the default Java behaviour that may display the top-down list off the screen (see #7917).
+ * Base class for all comboboxes in JOSM.
+ * <p>
+ * This combobox will show as many rows as possible without covering the combox itself. It makes
+ * sure the list will never go outside the screen (see #7917). You may limit the number of rows
+ * shown with the configuration: {@code gui.combobox.maximum-row-count}.
+ * <p>
+ * This combobox uses a {@link JosmTextField} for its editor component.
+ *
  * @param <E> the type of the elements of this combo box
- *
  * @since 5429 (creation)
  * @since 7015 (generics for Java 7)
  */
-public class JosmComboBox<E> extends JComboBox<E> {
-
-    private final ContextMenuHandler handler = new ContextMenuHandler();
-
-    /**
-     * Creates a <code>JosmComboBox</code> with a default data model.
+public class JosmComboBox<E> extends JComboBox<E> implements PopupMenuListener, PropertyChangeListener {
+    /**
+     * Limits the number of rows that this combobox will show.
+     */
+    public static final String PROP_MAXIMUM_ROW_COUNT = "gui.combobox.maximum-row-count";
+
+    /** the configured maximum row count or null */
+    private Integer configMaximumRowCount = null;
+
+    /**
+     * The preferred height of the combobox when closed.  Use if the items in the list dropdown are
+     * taller than the item in the editor, as in some comboboxes in the preset dialog.  -1 to use
+     * the height of the tallest item in the list.
+     */
+    private int preferredHeight = -1;
+
+    /** greyed text to display in the editor when the selected value is empty */
+    private String hint;
+
+    /**
+     * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model.
      * The default data model is an empty list of objects.
      * Use <code>addItem</code> to add items. By default the first item
      * in the data model becomes selected.
-     *
-     * @see DefaultComboBoxModel
      */
     public JosmComboBox() {
-        init(null);
-    }
-
-    /**
-     * Creates a <code>JosmComboBox</code> with a default data model and
+        super(new JosmComboBoxModel<E>());
+        init();
+    }
+
+    /**
+     * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model and
      * the specified prototype display value.
      * The default data model is an empty list of objects.
@@ -60,41 +82,82 @@
      *      displaying a scroll bar
      *
-     * @see DefaultComboBoxModel
      * @since 5450
-     */
+     * @deprecated use {@link #setPrototypeDisplayValue} instead.
+     */
+    @Deprecated
     public JosmComboBox(E prototypeDisplayValue) {
-        init(prototypeDisplayValue);
-    }
-
-    /**
-     * Creates a <code>JosmComboBox</code> that takes its items from an
-     * existing <code>ComboBoxModel</code>. Since the
-     * <code>ComboBoxModel</code> is provided, a combo box created using
-     * this constructor does not create a default combo box model and
-     * may impact how the insert, remove and add methods behave.
-     *
-     * @param aModel the <code>ComboBoxModel</code> that provides the
-     *      displayed list of items
-     * @see DefaultComboBoxModel
-     */
-    public JosmComboBox(ComboBoxModel<E> aModel) {
+        super(new JosmComboBoxModel<E>());
+        setPrototypeDisplayValue(prototypeDisplayValue);
+        init();
+    }
+
+    /**
+     * Creates a {@code JosmComboBox} that takes it items from an existing {@link JosmComboBoxModel}
+     * data model.
+     *
+     * @param aModel the model that provides the displayed list of items
+     */
+    public JosmComboBox(JosmComboBoxModel<E> aModel) {
         super(aModel);
-        List<E> list = IntStream.range(0, aModel.getSize())
-                .mapToObj(aModel::getElementAt)
-                .collect(Collectors.toList());
-        init(findPrototypeDisplayValue(list));
-    }
-
-    /**
-     * Creates a <code>JosmComboBox</code> that contains the elements
+        init();
+    }
+
+    /**
+     * Creates a {@code JosmComboBox} that takes it items from an existing {@link JosmComboBoxModel}
+     * data model and sets the specified prototype display value.
+     *
+     * @param aModel the model that provides the displayed list of items
+     * @param prototypeDisplayValue use this item to size the combobox (may be null)
+     * @deprecated use {@link #setPrototypeDisplayValue} instead.
+     */
+    @Deprecated
+    public JosmComboBox(JosmComboBoxModel<E> aModel, E prototypeDisplayValue) {
+        super(aModel);
+        setPrototypeDisplayValue(prototypeDisplayValue);
+        init();
+    }
+
+    /**
+     * Creates a {@code JosmComboBox} that contains the elements
      * in the specified array. By default the first item in the array
      * (and therefore the data model) becomes selected.
      *
      * @param items  an array of objects to insert into the combo box
-     * @see DefaultComboBoxModel
      */
     public JosmComboBox(E[] items) {
-        super(items);
-        init(findPrototypeDisplayValue(Arrays.asList(items)));
+        super(new JosmComboBoxModel<E>());
+        init();
+        for (E elem : items) {
+            getModel().addElement(elem);
+        }
+    }
+
+    private void init() {
+        configMaximumRowCount = Config.getPref().getInt(PROP_MAXIMUM_ROW_COUNT, 9999);
+        setEditor(new JosmComboBoxEditor());
+        // listen when the popup shows up so we can maximize its height
+        addPopupMenuListener(this);
+    }
+
+    /**
+     * Returns the {@link JosmComboBoxModel} currently used.
+     *
+     * @return the model or null
+     */
+    @Override
+    public JosmComboBoxModel<E> getModel() {
+        return (JosmComboBoxModel<E>) dataModel;
+    }
+
+    @Override
+    public void setEditor(ComboBoxEditor newEditor) {
+        if (editor != null) {
+            editor.getEditorComponent().removePropertyChangeListener(this);
+        }
+        super.setEditor(newEditor);
+        if (editor != null) {
+            // listen to orientation changes in the editor
+            editor.getEditorComponent().addPropertyChangeListener(this);
+        }
     }
 
@@ -105,6 +168,6 @@
      * @since 9484
      */
-    public JTextField getEditorComponent() {
-        return (JTextField) getEditor().getEditorComponent();
+    public JosmTextField getEditorComponent() {
+        return (JosmTextField) (editor == null ? null : editor.getEditorComponent());
     }
 
@@ -116,5 +179,6 @@
      */
     public String getText() {
-        return getEditorComponent().getText();
+        JosmTextField tf = getEditorComponent();
+        return tf == null ? null : tf.getText();
     }
 
@@ -126,182 +190,257 @@
      */
     public void setText(String value) {
-        getEditorComponent().setText(value);
-    }
-
-    /**
-     * Finds the prototype display value to use among the given possible candidates.
-     * @param possibleValues The possible candidates that will be iterated.
-     * @return The value that needs the largest display height on screen.
-     * @since 5558
-     */
-    protected final E findPrototypeDisplayValue(Collection<E> possibleValues) {
-        E result = null;
-        int maxHeight = -1;
-        if (possibleValues != null) {
-            // Remind old prototype to restore it later
-            E oldPrototype = getPrototypeDisplayValue();
-            // Get internal JList to directly call the renderer
-            @SuppressWarnings("rawtypes")
-            JList list = getList();
-            try {
-                // Index to give to renderer
-                int i = 0;
-                for (E value : possibleValues) {
-                    if (value != null) {
-                        // With a "classic" renderer, we could call setPrototypeDisplayValue(value) + getPreferredSize()
-                        // but not with TaggingPreset custom renderer that return a dummy height if index is equal to -1
-                        // So we explicitly call the renderer by simulating a correct index for the current value
-                        @SuppressWarnings("unchecked")
-                        Component c = getRenderer().getListCellRendererComponent(list, value, i, true, true);
-                        if (c != null) {
-                            // Get the real preferred size for the current value
-                            Dimension dim = c.getPreferredSize();
-                            if (dim.height > maxHeight) {
-                                // Larger ? This is our new prototype
-                                maxHeight = dim.height;
-                                result = value;
-                            }
-                        }
-                    }
-                    i++;
+        JosmTextField tf = getEditorComponent();
+        if (tf != null)
+            tf.setText(value);
+    }
+
+    /**
+     * Selects an item and/or sets text
+     *
+     * Selects the item whose {@code toString()} equals {@code text}. If an item could not be found,
+     * selects nothing and sets the text anyway.
+     *
+     * @param text the text to select and set
+     * @return the item or null
+     */
+    public E setSelectedItemText(String text) {
+        E item = getModel().find(text);
+        setSelectedItem(item);
+        if (text == null || !text.equals(getText()))
+            setText(text);
+        return item;
+    }
+
+    /* Hint handling */
+
+    /**
+     * Returns the hint text
+     * @return the hint text
+     */
+    public String getHint() {
+        return hint;
+    }
+
+    /**
+     * Sets the hint to display when no text has been entered.
+     *
+     * @param hint the hint to set
+     * @return the old hint
+     * @since 18221
+     */
+    public String setHint(String hint) {
+        String old = hint;
+        this.hint = hint;
+        JosmTextField tf = getEditorComponent();
+        if (tf != null)
+            tf.setHint(hint);
+        return old;
+    }
+
+    @Override
+    public void setComponentOrientation(ComponentOrientation o) {
+        if (o.isLeftToRight() != getComponentOrientation().isLeftToRight()) {
+            super.setComponentOrientation(o);
+            getEditorComponent().setComponentOrientation(o);
+            // the button doesn't move over without this
+            revalidate();
+        }
+    }
+
+    /**
+     * Return true if the combobox should display the hint text.
+     *
+     * @return whether to display the hint text
+     * @since 18221
+     */
+    public boolean displayHint() {
+        return !isEditable() && hint != null && !hint.isEmpty() && getText().isEmpty(); // && !isFocusOwner();
+    }
+
+    /**
+     * Overrides the calculated height.  See: {@link #setPreferredHeight(int)}.
+     *
+     * @since 18221
+     */
+    @Override
+    public Dimension getPreferredSize() {
+        Dimension d = super.getPreferredSize();
+        if (preferredHeight != -1)
+            d.height = preferredHeight;
+        return d;
+    }
+
+    /**
+     * Sets the preferred height of the combobox editor.
+     * <p>
+     * A combobox editor is automatically sized to accomodate the widest and the tallest items in
+     * the list.  In the Preset dialogs we show more of an item in the list than in the editor, so
+     * the editor becomes too big.  With this method we can set the editor height to a fixed value.
+     * <p>
+     * Set this to -1 to get the default behaviour back.
+     *
+     * See also: #6157
+     *
+     * @param height the preferred height or -1
+     * @return the old preferred height
+     * @see #setPreferredSize
+     * @since 18221
+     */
+    public int setPreferredHeight(int height) {
+        int old = preferredHeight;
+        preferredHeight = height;
+        return old;
+    }
+
+    /**
+     * Get the dropdown list component
+     *
+     * @return the list or null
+     */
+    @SuppressWarnings("unchecked")
+    public JList<E> getList() {
+        Object popup = getUI().getAccessibleChild(this, 0);
+        if (popup != null && popup instanceof javax.swing.plaf.basic.ComboPopup) {
+            return ((javax.swing.plaf.basic.ComboPopup) popup).getList();
+        }
+        return null;
+    }
+
+    // get the popup list
+
+    /**
+     * Draw the hint text for read-only comboboxes.
+     * <p>
+     * The obvious way -- to call {@code setText(hint)} and {@code setForeground(gray)} on the
+     * {@code JLabel} returned by the list cell renderer -- unfortunately does not work out well
+     * because many UIs change the foreground color or the enabled state of the {@code JLabel} after
+     * the list cell renderer has returned ({@code BasicComboBoxUI}).  Other UIs don't honor the
+     * label color at all ({@code SynthLabelUI}).
+     * <p>
+     * We use the same approach as in {@link JosmTextField}. The only problem we face is to get the
+     * coordinates of the text inside the combobox.  Fortunately even read-only comboboxes have a
+     * (partially configured) editor component, although they don't use it.  We configure that editor
+     * just enough to call {@link JTextField#modelToView modelToView} and
+     * {@link javax.swing.JComponent#getBaseline getBaseline} on it, thus obtaining the text
+     * coordinates.
+     *
+     * @see javax.swing.plaf.basic.BasicComboBoxUI#paintCurrentValue
+     * @see javax.swing.plaf.synth.SynthLabelUI#paint
+     */
+    @Override
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+        JosmTextField editor = getEditorComponent();
+        if (displayHint() && editor != null) {
+            if (editor.getSize().width == 0) {
+                Dimension dimen = getSize();
+                Insets insets = getInsets();
+                // a fake configuration not too far from reality
+                editor.setSize(dimen.width - insets.left - insets.right,
+                               dimen.height - insets.top - insets.bottom);
+            }
+            editor.drawHint(g);
+        }
+    }
+
+    /**
+     * Empties the internal undo manager, if any.
+     * <p>
+     * Used in the {@link org.openstreetmap.josm.gui.io.UploadDialog UploadDialog}.
+     * @since 14977
+     */
+    public final void discardAllUndoableEdits() {
+        getEditorComponent().discardAllUndoableEdits();
+    }
+
+    /**
+     * Limits the popup height.
+     * <p>
+     * Limits the popup height to the available screen space either below or above the combobox,
+     * whichever is bigger. To find the maximum number of rows that fit the screen, it does the
+     * reverse of the calculation done in
+     * {@link javax.swing.plaf.basic.BasicComboPopup#getPopupLocation}.
+     *
+     * @see javax.swing.plaf.basic.BasicComboBoxUI#getAccessibleChild
+     */
+    @Override
+    public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+        // Get the combobox bounds.
+        Rectangle bounds = new Rectangle(getLocationOnScreen(), getSize());
+
+        // Get the screen bounds of the screen (of a multi-screen setup) we are on.
+        Rectangle screenBounds;
+        GraphicsConfiguration gc = getGraphicsConfiguration();
+        Toolkit toolkit = Toolkit.getDefaultToolkit();
+        if (gc != null) {
+            Insets screenInsets = toolkit.getScreenInsets(gc);
+            screenBounds = gc.getBounds();
+            screenBounds.x += screenInsets.left;
+            screenBounds.y += screenInsets.top;
+            screenBounds.width -= (screenInsets.left + screenInsets.right);
+            screenBounds.height -= (screenInsets.top + screenInsets.bottom);
+        } else {
+            screenBounds = new Rectangle(new Point(), toolkit.getScreenSize());
+        }
+        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.
+            JList<E> jList = getList();
+
+            // 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
+            // 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();
+                Border border = scroller.getViewportBorder();
+                if (border != null) {
+                    insets = border.getBorderInsets(null);
+                    free -= insets.top + insets.bottom;
                 }
-            } finally {
-                // Restore original prototype
-                setPrototypeDisplayValue(oldPrototype);
-            }
-        }
-        return result;
-    }
-
-    @SuppressWarnings("unchecked")
-    protected final JList<Object> getList() {
-        return IntStream.range(0, getUI().getAccessibleChildrenCount(this))
-                .mapToObj(i -> getUI().getAccessibleChild(this, i))
-                .filter(child -> child instanceof ComboPopup)
-                .findFirst()
-                .map(child -> ((ComboPopup) child).getList())
-                .orElse(null);
-    }
-
-    /**
-     * Set the prototypeCellValue property and calculate the height of the dropdown.
-     */
-    @Override
-    public void setPrototypeDisplayValue(E prototype) {
-        if (prototype != null) {
-            super.setPrototypeDisplayValue(prototype);
-            int screenHeight = GuiHelper.getScreenSize().height;
-            // Compute maximum number of visible items based on the preferred size of the combo box.
-            // This assumes that items have the same height as the combo box, which is not granted by the look and feel
-            int maxsize = (screenHeight/getPreferredSize().height) / 2;
-            // If possible, adjust the maximum number of items with the real height of items
-            // It is not granted this works on every platform (tested OK on Windows)
-            JList<Object> list = getList();
-            if (list != null) {
-                if (!prototype.equals(list.getPrototypeCellValue())) {
-                    list.setPrototypeCellValue(prototype);
-                }
-                int height = list.getFixedCellHeight();
-                if (height > 0) {
-                    maxsize = (screenHeight/height) / 2;
+                border = scroller.getBorder();
+                if (border != null) {
+                    insets = border.getBorderInsets(null);
+                    free -= insets.top + insets.bottom;
                 }
             }
-            setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize));
-        }
-    }
-
-    protected final void init(E prototype) {
-        init(prototype, true);
-    }
-
-    protected final void init(E prototype, boolean registerPropertyChangeListener) {
-        setPrototypeDisplayValue(prototype);
-        // Handle text contextual menus for editable comboboxes
-        if (registerPropertyChangeListener) {
-            addPropertyChangeListener("editable", handler);
-            addPropertyChangeListener("editor", handler);
-        }
-    }
-
-    protected class ContextMenuHandler extends MouseAdapter implements PropertyChangeListener {
-
-        private JTextComponent component;
-        private PopupMenuLauncher launcher;
-
-        @Override
-        public void propertyChange(PropertyChangeEvent evt) {
-            if ("editable".equals(evt.getPropertyName())) {
-                if (evt.getNewValue().equals(Boolean.TRUE)) {
-                    enableMenu();
-                } else {
-                    disableMenu();
-                }
-            } else if ("editor".equals(evt.getPropertyName())) {
-                disableMenu();
-                if (isEditable()) {
-                    enableMenu();
-                }
+
+            // Calculate how many rows fit into the free space.  Rows may have variable heights.
+            int rowCount = Math.min(configMaximumRowCount, getItemCount());
+            ListCellRenderer<? super E> r = jList.getCellRenderer();  // must take this from list, not combo: flatlaf bug
+            int i, h = 0;
+            for (i = 0; i < rowCount; ++i) {
+                Component c = r.getListCellRendererComponent(jList, getModel().getElementAt(i), i, false, false);
+                h += c.getPreferredSize().height;
+                if (h >= free)
+                    break;
             }
-        }
-
-        private void enableMenu() {
-            if (launcher == null && editor != null) {
-                Component editorComponent = editor.getEditorComponent();
-                if (editorComponent instanceof JTextComponent) {
-                    component = (JTextComponent) editorComponent;
-                    component.addMouseListener(this);
-                    launcher = TextContextualPopupMenu.enableMenuFor(component, true);
-                }
-            }
-        }
-
-        private void disableMenu() {
-            if (launcher != null) {
-                TextContextualPopupMenu.disableMenuFor(component, launcher);
-                launcher = null;
-                component.removeMouseListener(this);
-                component = null;
-            }
-        }
-
-        private void discardAllUndoableEdits() {
-            if (launcher != null) {
-                launcher.discardAllUndoableEdits();
-            }
-        }
-
-        @Override
-        public void mousePressed(MouseEvent e) {
-            processEvent(e);
-        }
-
-        @Override
-        public void mouseReleased(MouseEvent e) {
-            processEvent(e);
-        }
-
-        private void processEvent(MouseEvent e) {
-            if (launcher != null && !e.isPopupTrigger() && launcher.getMenu().isShowing()) {
-                launcher.getMenu().setVisible(false);
-            }
-        }
-    }
-
-    /**
-     * Reinitializes this {@link JosmComboBox} to the specified values. This may be needed if a custom renderer is used.
-     * @param values The values displayed in the combo box.
-     * @since 5558
-     */
-    public final void reinitialize(Collection<E> values) {
-        init(findPrototypeDisplayValue(values), false);
-        discardAllUndoableEdits();
-    }
-
-    /**
-     * Empties the internal undo manager, if any.
-     * @since 14977
-     */
-    public final void discardAllUndoableEdits() {
-        handler.discardAllUndoableEdits();
+            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
+        }
+    }
+
+    @Override
+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+        // Who cares?
+    }
+
+    @Override
+    public void popupMenuCanceled(PopupMenuEvent e) {
+        // Who cares?
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        // follow our editor's orientation
+        if ("componentOrientation".equals(evt.getPropertyName())) {
+            setComponentOrientation((ComponentOrientation) evt.getNewValue());
+        }
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxEditor.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxEditor.java	(revision 18221)
@@ -0,0 +1,26 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.widgets;
+
+import javax.swing.plaf.basic.BasicComboBoxEditor;
+
+/**
+ * A {@link javax.swing.ComboBoxEditor} that uses an {@link JosmTextField}.
+ * <p>
+ * This lets us stick a {@code JosmTextField} into a {@link javax.swing.JComboBox}.
+ * Used in {@link JosmComboBox}.
+ *
+ * @since 18221
+ */
+public class JosmComboBoxEditor extends BasicComboBoxEditor {
+
+    @Override
+    protected JosmTextField createEditorComponent() {
+        return new JosmTextField();
+    }
+
+    @Override
+    public JosmTextField getEditorComponent() {
+        // this cast holds unless somebody overrides createEditorComponent()
+        return (JosmTextField) editor;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java	(revision 18221)
@@ -0,0 +1,322 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.widgets;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.swing.AbstractListModel;
+import javax.swing.MutableComboBoxModel;
+
+import org.openstreetmap.josm.data.preferences.ListProperty;
+import org.openstreetmap.josm.spi.preferences.Config;
+
+/**
+ * A data model for the {@link JosmComboBox}
+ *
+ * @author marcello@perathoner.de
+ * @param <E> The element type.
+ * @since 18221
+ */
+public class JosmComboBoxModel<E> extends AbstractListModel<E> implements MutableComboBoxModel<E>, Iterable<E> {
+
+    /** The maximum number of elements to hold, -1 for no limit. Used for histories. */
+    private int maxSize = -1;
+
+    /** the elements shown in the dropdown */
+    protected ArrayList<E> elements = new ArrayList<>();
+    /** the selected element in the dropdown or null */
+    protected Object selected;
+
+    /**
+     * Sets the maximum number of elements.
+     *
+     * @param size The maximal number of elements in the model.
+     */
+    public void setSize(int size) {
+        maxSize = size;
+    }
+
+    /**
+     * Returns a copy of the element list.
+     * @return a copy of the data
+     */
+    public Collection<E> asCollection() {
+        return new ArrayList<>(elements);
+    }
+
+    /**
+     * Returns the index of the specified element
+     *
+     * Note: This is not part of the {@link javax.swing.ComboBoxModel} interface but is defined in
+     * {@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
+     */
+    public int getIndexOf(E element) {
+        return elements.indexOf(element);
+    }
+
+    //
+    // interface java.lang.Iterable
+    //
+
+    @Override
+    public Iterator<E> iterator() {
+        return elements.iterator();
+    }
+
+    //
+    // interface javax.swing.MutableComboBoxModel
+    //
+
+    /**
+     * Adds an element to the end of the model. Does nothing if max size is already reached.
+     */
+    @Override
+    public void addElement(E element) {
+        if (element != null && (maxSize == -1 || getSize() < maxSize)) {
+            elements.add(element);
+        }
+    }
+
+    @Override
+    public void removeElement(Object elem) {
+        elements.remove(elem);
+    }
+
+    @Override
+    public void removeElementAt(int index) {
+        Object elem = getElementAt(index);
+        if (elem == selected) {
+            if (index == 0) {
+                setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1));
+            } else {
+                setSelectedItem(getElementAt(index - 1));
+            }
+        }
+        elements.remove(index);
+        fireIntervalRemoved(this, index, index);
+    }
+
+    /**
+     * Adds an element at a specific index.
+     *
+     * @param element The element to add
+     * @param index Location to add the element
+     */
+    @Override
+    public void insertElementAt(E element, int index) {
+        if (maxSize != -1 && maxSize <= getSize()) {
+            removeElementAt(getSize() - 1);
+        }
+        elements.add(index, element);
+    }
+
+    //
+    // javax.swing.ComboBoxModel
+    //
+
+    /**
+     * Set the value of the selected item. The selected item may be null.
+     *
+     * @param elem The combo box value or null for no selection.
+     */
+    @Override
+    public void setSelectedItem(Object elem) {
+        if ((selected != null && !selected.equals(elem)) ||
+            (selected == null && elem != null)) {
+            selected = elem;
+            fireContentsChanged(this, -1, -1);
+        }
+    }
+
+    @Override
+    public Object getSelectedItem() {
+        return selected;
+    }
+
+    //
+    // javax.swing.ListModel
+    //
+
+    @Override
+    public int getSize() {
+        return elements.size();
+    }
+
+    @Override
+    public E getElementAt(int index) {
+        if (index >= 0 && index < elements.size())
+            return elements.get(index);
+        else
+            return null;
+    }
+
+    //
+    // end interfaces
+    //
+
+    /**
+     * Adds all elements from the collection.
+     *
+     * @param elems The elements to add.
+     */
+    public void addAllElements(Collection<E> elems) {
+        elems.forEach(e -> addElement(e));
+    }
+
+    /**
+     * Adds all elements from the collection of string representations.
+     *
+     * @param strings The string representation of the elements to add.
+     * @param buildE A {@link java.util.function.Function} that builds an {@code <E>} from a
+     *               {@code String}.
+     */
+    public void addAllElements(Collection<String> strings, Function<String, E> buildE) {
+        strings.forEach(s -> addElement(buildE.apply(s)));
+    }
+
+    /**
+     * 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,
+     * deletes the last element.
+     *
+     * @param newElement the element to add
+     * @return The element that is at the top now.
+     */
+    public E addTopElement(E newElement) {
+        // if the element is already at the top, do nothing
+        if (newElement.equals(getElementAt(0)))
+            return getElementAt(0);
+
+        removeElement(newElement);
+        insertElementAt(newElement, 0);
+        return newElement;
+    }
+
+    /**
+     * Empties the list.
+     */
+    public void removeAllElements() {
+        if (!elements.isEmpty()) {
+            int firstIndex = 0;
+            int lastIndex = elements.size() - 1;
+            elements.clear();
+            selected = null;
+            fireIntervalRemoved(this, firstIndex, lastIndex);
+        } else {
+            selected = null;
+        }
+    }
+
+    /**
+     * Finds the item that matches string.
+     * <p>
+     * Looks in the model for an element whose {@code toString()} matches {@code s}.
+     *
+     * @param s The string to match.
+     * @return The item or null
+     */
+    public E find(String s) {
+        return elements.stream().filter(o -> o.toString().equals(s)).findAny().orElse(null);
+    }
+
+    /**
+     * Gets a preference loader and saver.
+     *
+     * @param readE A {@link Function} that builds an {@code <E>} from a {@link String}.
+     * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
+     * @return The {@link Preferences} instance.
+     */
+    public Preferences prefs(Function<String, E> readE, Function<E, String> writeE) {
+        return new Preferences(readE, writeE);
+    }
+
+    /**
+     * Loads and saves the model to the JOSM preferences.
+     * <p>
+     * Obtainable through {@link #prefs}.
+     */
+    public final class Preferences {
+
+        /** A {@link Function} that builds an {@code <E>} from a {@code String}. */
+        private Function<String, E> readE;
+        /** A {@code Function} that serializes {@code <E>} to a {@code String}. */
+        private Function<E, String> writeE;
+
+        /**
+         * Private constructor
+         *
+         * @param readE A {@link Function} that builds an {@code <E>} from a {@code String}.
+         * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
+         */
+        private Preferences(Function<String, E> readE, Function<E, String> writeE) {
+            this.readE = readE;
+            this.writeE = writeE;
+        }
+
+        /**
+         * Loads the model from the JOSM preferences.
+         * @param key The preferences key
+         */
+        public void load(String key) {
+            removeAllElements();
+            addAllElements(Config.getPref().getList(key), readE);
+        }
+
+        /**
+         * Loads the model from the JOSM preferences.
+         *
+         * @param key The preferences key
+         * @param defaults A list of default values.
+         */
+        public void load(String key, List<String> defaults) {
+            removeAllElements();
+            addAllElements(Config.getPref().getList(key, defaults), readE);
+        }
+
+        /**
+         * Loads the model from the JOSM preferences.
+         *
+         * @param prop The property holding the strings.
+         */
+        public void load(ListProperty prop) {
+            removeAllElements();
+            addAllElements(prop.get(), readE);
+        }
+
+        /**
+         * Returns the model elements as list of strings.
+         *
+         * @return a list of strings
+         */
+        public List<String> asStringList() {
+            List<String> list = new ArrayList<>(getSize());
+            forEach(element -> list.add(writeE.apply(element)));
+            return list;
+        }
+
+        /**
+         * Saves the model to the JOSM preferences.
+         *
+        * @param key The preferences key
+        */
+        public void save(String key) {
+            Config.getPref().putList(key, asStringList());
+        }
+
+        /**
+         * Saves the model to the JOSM preferences.
+         *
+         * @param prop The property to write to.
+         */
+        public void save(ListProperty prop) {
+            prop.put(asStringList());
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/JosmListCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/JosmListCellRenderer.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/JosmListCellRenderer.java	(revision 18221)
@@ -0,0 +1,35 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.widgets;
+
+import java.awt.Component;
+
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+
+/**
+ * A convenience list cell renderer to override.
+ *
+ * @param <E> The type of the ListCellRenderer
+ * @since 18221
+ */
+public class JosmListCellRenderer<E> implements ListCellRenderer<E> {
+    protected ListCellRenderer<? super E> renderer;
+    protected Component component;
+
+    /**
+     * Constructor.
+     * @param component the component
+     * @param renderer The inner renderer
+     */
+    public JosmListCellRenderer(Component component, ListCellRenderer<? super E> renderer) {
+        this.component = component;
+        this.renderer = renderer;
+    }
+
+    @Override
+    public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) {
+        Component l = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+        l.setComponentOrientation(component.getComponentOrientation());
+        return l;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java	(revision 18220)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java	(revision 18221)
@@ -3,16 +3,26 @@
 
 import java.awt.Color;
+import java.awt.ComponentOrientation;
+import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Insets;
+import java.awt.Point;
 import java.awt.RenderingHints;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
-
-import javax.swing.BorderFactory;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
 import javax.swing.Icon;
 import javax.swing.JTextField;
-import javax.swing.border.Border;
+import javax.swing.RepaintManager;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.UIManager;
+import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 
@@ -31,10 +41,12 @@
  * @since 5886
  */
-public class JosmTextField extends JTextField implements Destroyable, FocusListener {
+public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener {
 
     private final PopupMenuLauncher launcher;
     private String hint;
     private Icon icon;
-    private int leftInsets;
+    private Point iconPos;
+    private Insets originalMargin;
+    private OrientationAction orientationAction;
 
     /**
@@ -78,4 +90,19 @@
         super(doc, text, columns);
         launcher = TextContextualPopupMenu.enableMenuFor(this, undoRedo);
+
+        // There seems to be a bug in Swing 8 that components with Bidi enabled are smaller than
+        // without. (eg. 23px vs 21px in height, maybe a font thing).  Usually Bidi starts disabled
+        // but gets enabled whenever RTL text is loaded.  To avoid trashing the layout we enable
+        // Bidi by default.  See also {@link #drawHint()}.
+        getDocument().putProperty("i18n", Boolean.TRUE);
+
+        // the menu and hotkey to change text orientation
+        orientationAction = new OrientationAction(this);
+        orientationAction.addPropertyChangeListener(this);
+        JPopupMenu menu = launcher.getMenu();
+        menu.addSeparator();
+        menu.add(new JMenuItem(orientationAction));
+        getInputMap().put(OrientationAction.getShortcutKey(), orientationAction);
+
         // Fix minimum size when columns are specified
         if (columns > 0) {
@@ -83,6 +110,8 @@
         }
         addFocusListener(this);
+        addComponentListener(this);
         // Workaround for Java bug 6322854
         JosmPasswordField.workaroundJdkBug6322854(this);
+        originalMargin = getMargin();
     }
 
@@ -148,8 +177,21 @@
      * Sets the hint to display when no text has been entered.
      * @param hint the hint to set
-     * @since 7505
-     */
-    public final void setHint(String hint) {
+     * @return the old hint
+     * @since 18221 (signature)
+     */
+    public String setHint(String hint) {
+        String old = hint;
         this.hint = hint;
+        return old;
+    }
+
+    /**
+     * Return true if the textfield should display the hint text.
+     *
+     * @return whether to display the hint text
+     * @since 18221
+     */
+    public boolean displayHint() {
+        return !Utils.isEmpty(hint) && getText().isEmpty() && !isFocusOwner();
     }
 
@@ -170,9 +212,30 @@
     public void setIcon(Icon icon) {
         this.icon = icon;
+        if (icon == null) {
+            setMargin(originalMargin);
+        }
+        positionIcon();
+    }
+
+    private void positionIcon() {
         if (icon != null) {
-            this.leftInsets = getInsets().left;
-            Border original = getBorder();
-            Border margin = BorderFactory.createEmptyBorder(0, icon.getIconWidth(), 0, 0);
-            setBorder(original == null ? margin : BorderFactory.createCompoundBorder(original, margin));
+            Insets margin = (Insets) originalMargin.clone();
+            int hGap = (getHeight() - icon.getIconHeight()) / 2;
+            if (getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) {
+                margin.right += icon.getIconWidth() + 2 * hGap;
+                iconPos = new Point(getWidth() - icon.getIconWidth() - hGap, hGap);
+            } else {
+                margin.left += icon.getIconWidth() + 2 * hGap;
+                iconPos = new Point(hGap, hGap);
+            }
+            setMargin(margin);
+        }
+    }
+
+    @Override
+    public void setComponentOrientation(ComponentOrientation o) {
+        if (o.isLeftToRight() != getComponentOrientation().isLeftToRight()) {
+            super.setComponentOrientation(o);
+            positionIcon();
         }
     }
@@ -186,27 +249,67 @@
     }
 
-    @Override
-    public void paint(Graphics g) {
-        super.paint(g);
+    /**
+     * Returns the color for hint texts.
+     * @return the Color for hint texts
+     */
+    public static Color getHintTextColor() {
+        Color color = UIManager.getColor("TextField[Disabled].textForeground"); // Nimbus?
+        if (color == null)
+            color = UIManager.getColor("TextField.inactiveForeground");
+        if (color == null)
+            color = Color.GRAY;
+        return color;
+    }
+
+    /**
+     * Returns the font for hint texts.
+     * @return the font for hint texts
+     */
+    public static Font getHintFont() {
+        return UIManager.getFont("TextField.font");
+    }
+
+    @Override
+    public void paintComponent(Graphics g) {
+        super.paintComponent(g);
         if (icon != null) {
-            int h = getHeight() - icon.getIconHeight();
-            icon.paintIcon(this, g, Math.min(leftInsets, h / 2), h / 2);
-        }
-        if (!Utils.isEmpty(hint) && getText().isEmpty() && !isFocusOwner()) {
-            // Taken from http://stackoverflow.com/a/24571681/2257172
-            int h = getHeight();
-            if (g instanceof Graphics2D) {
-                ((Graphics2D) g).setRenderingHint(
-                        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-            }
-            Insets ins = getInsets();
-            FontMetrics fm = g.getFontMetrics();
-            int c0 = getBackground().getRGB();
-            int c1 = getForeground().getRGB();
-            int m = 0xfefefefe;
-            int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1);
-            g.setColor(new Color(c2, true));
-            g.drawString(hint, ins.left, h / 2 + fm.getAscent() / 2 - 2);
-        }
+            icon.paintIcon(this, g, iconPos.x, iconPos.y);
+        }
+        if (displayHint()) {
+            // Logging.debug("drawing textfield hint: {0}", getHint());
+            drawHint(g);
+        }
+    }
+
+    /**
+     * Draws the hint text over the editor component.
+     *
+     * @param g the graphics context
+     */
+    public void drawHint(Graphics g) {
+        int x;
+        try {
+            x = modelToView(0).x;
+        } catch (BadLocationException exc) {
+            return; // can't happen
+        }
+        // Taken from http://stackoverflow.com/a/24571681/2257172
+        if (g instanceof Graphics2D) {
+            ((Graphics2D) g).setRenderingHint(
+                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        }
+        g.setColor(getHintTextColor());
+        g.setFont(getHintFont());
+        if (getComponentOrientation().isLeftToRight()) {
+            g.drawString(getHint(), x, getBaseline(getWidth(), getHeight()));
+        } else {
+            FontMetrics metrics = g.getFontMetrics(g.getFont());
+            int dx = metrics.stringWidth(getHint());
+            g.drawString(getHint(), x - dx, getBaseline(getWidth(), getHeight()));
+        }
+        // Needed to avoid endless repaint loop if we accidentally draw over the insets.  This may
+        // easily happen because a change in text orientation invalidates the textfield and
+        // following that the preferred size gets smaller. (Bug in Swing?)
+        RepaintManager.currentManager(this).markCompletelyClean(this);
     }
 
@@ -217,5 +320,11 @@
             map.keyDetector.setEnabled(false);
         }
-        repaint();
+        if (e != null && e.getOppositeComponent() != null) {
+            // Select all characters when the change of focus occurs inside JOSM only.
+            // When switching from another application, it is annoying, see #13747
+            selectAll();
+        }
+        positionIcon();
+        repaint(); // get rid of hint
     }
 
@@ -226,5 +335,5 @@
             map.keyDetector.setEnabled(true);
         }
-        repaint();
+        repaint(); // paint hint
     }
 
@@ -234,3 +343,28 @@
         TextContextualPopupMenu.disableMenuFor(this, launcher);
     }
+
+    @Override
+    public void componentResized(ComponentEvent e) {
+        positionIcon();
+    }
+
+    @Override
+    public void componentMoved(ComponentEvent e) {
+    }
+
+    @Override
+    public void componentShown(ComponentEvent e) {
+    }
+
+    @Override
+    public void componentHidden(ComponentEvent e) {
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        // command from the menu / shortcut key
+        if ("orientationAction".equals(evt.getPropertyName())) {
+            setComponentOrientation((ComponentOrientation) evt.getNewValue());
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/widgets/OrientationAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/widgets/OrientationAction.java	(revision 18221)
+++ /trunk/src/org/openstreetmap/josm/gui/widgets/OrientationAction.java	(revision 18221)
@@ -0,0 +1,211 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.widgets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.ComponentOrientation;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.KeyStroke;
+
+import org.openstreetmap.josm.data.preferences.ListProperty;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.PlatformManager;
+
+/**
+ * An action that toggles text orientation.
+ * @since 18221
+ */
+public class OrientationAction extends AbstractAction implements PropertyChangeListener {
+    /** Default for {@link #RTL_LANGUAGES} */
+    public static final List<String> DEFAULT_RTL_LANGUAGES = Arrays.asList("ar", "he", "fa", "iw", "ur", "lld");
+
+    /** Default for {@link #LOCALIZED_KEYS} */
+    public static final List<String> DEFAULT_LOCALIZED_KEYS = Arrays.asList(
+        "(\\p{Alnum}+_)?name", "addr", "description", "fixme", "note", "source", "strapline", "operator");
+
+    /**
+     * Language codes of languages that are right-to-left
+     *
+     * @see #getValueOrientation
+     */
+    public static final ListProperty RTL_LANGUAGES = new ListProperty("properties.rtl-languages", DEFAULT_RTL_LANGUAGES);
+    /**
+     * Keys whose values are localized
+     *
+     * Regex fractions are allowed. The items will be merged into a regular expression.
+     *
+     * @see #getValueOrientation
+     */
+    public static final ListProperty LOCALIZED_KEYS = new ListProperty("properties.localized-keys", DEFAULT_LOCALIZED_KEYS);
+
+    private static final Pattern LANG_PATTERN = Pattern.compile(":([a-z]{2,3})$");
+
+    private Component component;
+    private ImageIcon iconRTL;
+    private ImageIcon iconLTR;
+    protected static Set<String> RTLLanguages = new HashSet<>(RTL_LANGUAGES.get());
+    protected static Pattern localizedKeys = compile_localized_keys();
+
+    /**
+     * Constructs a new {@code OrientationAction}.
+     *
+     * @param component The component to toggle
+     */
+    public OrientationAction(Component component) {
+        super(null);
+        this.component = component;
+        setEnabled(true);
+        if (Config.getPref().getBoolean("text.popupmenu.useicons", true)) {
+            iconLTR = new ImageProvider("dialogs/next").setSize(ImageProvider.ImageSizes.SMALLICON).get();
+            iconRTL = new ImageProvider("dialogs/previous").setSize(ImageProvider.ImageSizes.SMALLICON).get();
+        }
+        component.addPropertyChangeListener(this);
+        putValue(Action.ACCELERATOR_KEY, getShortcutKey());
+        updateState();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        firePropertyChange("orientationAction", null, getValue("newState"));
+    }
+
+    /**
+     * Updates the text and the icon.
+     */
+    public void updateState() {
+        if (component.getComponentOrientation().isLeftToRight()) {
+            putValue(Action.NAME, tr("Right to Left"));
+            putValue(Action.SMALL_ICON, iconRTL);
+            putValue(Action.SHORT_DESCRIPTION, tr("Switch the text orientation to Right-to-Left."));
+            putValue("newState", ComponentOrientation.RIGHT_TO_LEFT);
+        } else {
+            putValue(Action.NAME, tr("Left to Right"));
+            putValue(Action.SMALL_ICON, iconLTR);
+            putValue(Action.SHORT_DESCRIPTION, tr("Switch the text orientation to Left-to-Right."));
+            putValue("newState", ComponentOrientation.LEFT_TO_RIGHT);
+        }
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        if ("componentOrientation".equals(evt.getPropertyName())) {
+            updateState();
+        }
+    }
+
+    /**
+     * Returns the shortcut key to assign to this action.
+     *
+     * @return the shortcut key
+     */
+    public static KeyStroke getShortcutKey() {
+        return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, PlatformManager.getPlatform().getMenuShortcutKeyMaskEx());
+    }
+
+    /**
+     * Returns the default component orientation by the user's locale
+     *
+     * @return the default component orientation
+     */
+    public static ComponentOrientation getDefaultComponentOrientation() {
+        Component main = MainApplication.getMainFrame();
+        // is null while testing
+        return main != null ? main.getComponentOrientation() : ComponentOrientation.LEFT_TO_RIGHT;
+    }
+
+    /**
+     * Returns the text orientation of the value for the given key.
+     *
+     * This is intended for Preset Dialog comboboxes. The choices in the dropdown list are
+     * typically translated. Ideally the user needs not see the English value.
+     *
+     * The algorithm is as follows:
+     * <ul>
+     * <li>If the key has an explicit language suffix, return the text orientation for that
+     * language.
+     * <li>Else return the text orientation of the user's locale.
+     * </ul>
+     *
+     * You can configure which languages are RTL with the list property: {@code rtl-languages}.
+     *
+     * @param key the key
+     * @return the text orientation of the value
+     */
+    public static ComponentOrientation getValueOrientation(String key) {
+        if (key == null || key.isEmpty())
+            return ComponentOrientation.LEFT_TO_RIGHT;
+
+        // if the key has an explicit language suffix, use it
+        Matcher m = LANG_PATTERN.matcher(key);
+        if (m.find()) {
+            if (RTLLanguages.contains(m.group(1))) {
+                return ComponentOrientation.RIGHT_TO_LEFT;
+            }
+            return ComponentOrientation.LEFT_TO_RIGHT;
+        }
+        // return the user's locale
+        return ComponentOrientation.getOrientation(Locale.getDefault());
+    }
+
+    /**
+     * Returns the text orientation of the value for the given key.
+     *
+     * This expansion of {@link #getValueOrientation} is intended for Preset Dialog textfields and
+     * for the Add Tag and Edit Tag dialog comboboxes.
+     *
+     * The algorithm is as follows:
+     * <ul>
+     * <li>If the key has an explicit language suffix, return the text orientation for that
+     * language.
+     * <li>If the key is usually localized, return the text orientation of the user's locale.
+     * <li>Else return left to right.
+     * </ul>
+     *
+     * You can configure which keys are localized with the list property: {@code localized-keys}.
+     * You can configure which languages are RTL with the list property: {@code rtl-languages}.
+     *
+     * @param key the key
+     * @return the text orientation of the value
+     */
+    public static ComponentOrientation getNamelikeOrientation(String key) {
+        if (key == null || key.isEmpty())
+            return ComponentOrientation.LEFT_TO_RIGHT;
+
+        // if the key has an explicit language suffix, use it
+        Matcher m = LANG_PATTERN.matcher(key);
+        if (m.find()) {
+            if (RTLLanguages.contains(m.group(1))) {
+                return ComponentOrientation.RIGHT_TO_LEFT;
+            }
+            return ComponentOrientation.LEFT_TO_RIGHT;
+        }
+        // if the key is usually localized, use the user's locale
+        m = localizedKeys.matcher(key);
+        if (m.find()) {
+            return ComponentOrientation.getOrientation(Locale.getDefault());
+        }
+        // all other keys are LTR
+        return ComponentOrientation.LEFT_TO_RIGHT;
+    }
+
+    private static Pattern compile_localized_keys() {
+        return Pattern.compile("^(" + String.join("|", LOCALIZED_KEYS.get()) + ")$");
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditorTest.java	(revision 18220)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditorTest.java	(revision 18221)
@@ -5,8 +5,10 @@
 
 import org.junit.jupiter.api.Test;
+import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
 
 /**
  * Unit tests of {@link MultiValueCellEditor} class.
  */
+@BasicPreferences
 class MultiValueCellEditorTest {
     /**
Index: /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntryTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntryTest.java	(revision 18220)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntryTest.java	(revision 18221)
@@ -2,5 +2,5 @@
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.BeforeAll;
@@ -26,5 +26,5 @@
     @Test
     void testTicket12416() {
-        assertEquals("&nbsp;", new PresetListEntry("").getListDisplay());
+        assertTrue(new PresetListEntry("").getListDisplay(200).contains(" "));
     }
 }
