Index: /trunk/scripts/taginfoextract.groovy
===================================================================
--- /trunk/scripts/taginfoextract.groovy	(revision 8862)
+++ /trunk/scripts/taginfoextract.groovy	(revision 8863)
@@ -37,8 +37,9 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference
-import org.openstreetmap.josm.gui.tagging.TaggingPreset
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems
-import org.openstreetmap.josm.gui.tagging.TaggingPresetReader
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem.MatchType
 import org.openstreetmap.josm.io.CachedFile
 import org.openstreetmap.josm.tools.Utils
@@ -252,9 +253,9 @@
         def tags = []
         for (TaggingPreset preset : presets) {
-            for (TaggingPresetItems.KeyedItem item : Utils.filteredCollection(preset.data, TaggingPresetItems.KeyedItem.class)) {
+            for (KeyedItem item : Utils.filteredCollection(preset.data, KeyedItem.class)) {
                 def values
-                switch (TaggingPresetItems.MatchType.ofString(item.match)) {
-                    case TaggingPresetItems.MatchType.KEY_REQUIRED: values = item.getValues(); break;
-                    case TaggingPresetItems.MatchType.KEY_VALUE_REQUIRED: values = item.getValues(); break;
+                switch (MatchType.ofString(item.match)) {
+                    case MatchType.KEY_REQUIRED: values = item.getValues(); break;
+                    case MatchType.KEY_VALUE_REQUIRED: values = item.getValues(); break;
                     default: values = [];
                 }
@@ -463,5 +464,3 @@
         }
     }
-
 }
-
Index: /trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/Main.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/Main.java	(revision 8863)
@@ -99,5 +99,5 @@
 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitorExecutor;
-import org.openstreetmap.josm.gui.tagging.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.gui.util.RedirectInputMap;
 import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 8863)
@@ -21,11 +21,11 @@
 import org.openstreetmap.josm.data.validation.Test;
 import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
-import org.openstreetmap.josm.gui.tagging.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
 import org.openstreetmap.josm.tools.Utils;
 
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 8863)
@@ -45,10 +45,10 @@
 import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Check;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.CheckGroup;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.KeyedItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.items.Check;
+import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
 import org.openstreetmap.josm.gui.widgets.EditableList;
 import org.openstreetmap.josm.io.CachedFile;
Index: /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 8863)
@@ -37,6 +37,6 @@
 import org.openstreetmap.josm.data.osm.history.HistoryRelation;
 import org.openstreetmap.josm.data.osm.history.HistoryWay;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetNameTemplateList;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetNameTemplateList;
 import org.openstreetmap.josm.tools.AlphanumComparator;
 import org.openstreetmap.josm.tools.I18n;
Index: /trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 8863)
@@ -130,6 +130,6 @@
 import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetSearchAction;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetSearchPrimitiveDialog;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchAction;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchPrimitiveDialog;
 import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
 import org.openstreetmap.josm.tools.Shortcut;
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java	(revision 8863)
@@ -3,8 +3,8 @@
 
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.tagging.PresetHandler;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
-import org.openstreetmap.josm.gui.tagging.PresetLabel;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.tools.GBC;
 
@@ -34,5 +34,5 @@
      * and associates an interaction with (matching) presets via {@code presetHandler}.
      */
-    public void updatePresets(final Collection<TaggingPresetType> types, final Map<String, String> tags, final PresetHandler presetHandler) {
+    public void updatePresets(final Collection<TaggingPresetType> types, final Map<String, String> tags, final TaggingPresetHandler presetHandler) {
 
         removeAll();
@@ -43,5 +43,5 @@
 
         for (final TaggingPreset t : TaggingPreset.getMatchingPresets(types, tags, true)) {
-            final JLabel lbl = new PresetLabel(t);
+            final JLabel lbl = new TaggingPresetLabel(t);
             lbl.addMouseListener(new MouseAdapter() {
                 @Override
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 8863)
@@ -85,7 +85,7 @@
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.PresetHandler;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
@@ -215,5 +215,5 @@
             + tr("Select objects for which to change tags.") + "</p></html>");
 
-    private final transient PresetHandler presetHandler = new PresetHandler() {
+    private final transient TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
         @Override public void updateTags(List<Tag> tags) {
             Command command = TaggingPreset.createCommand(getSelection(), tags);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 8863)
@@ -69,8 +69,8 @@
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 8863)
@@ -79,11 +79,11 @@
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.PresetHandler;
 import org.openstreetmap.josm.gui.tagging.TagEditorModel;
 import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.io.OnlineResource;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
@@ -137,5 +137,5 @@
                 WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650)));
 
-        final PresetHandler presetHandler = new PresetHandler() {
+        final TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
 
             @Override
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 8863)
@@ -39,7 +39,7 @@
 import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.PresetHandler;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
 
@@ -56,5 +56,5 @@
     private final CopyOnWriteArrayList<IMemberModelListener> listeners;
     private final transient OsmDataLayer layer;
-    private final transient PresetHandler presetHandler;
+    private final transient TaggingPresetHandler presetHandler;
 
     private final transient WayConnectionTypeCalculator wayConnectionTypeCalculator = new WayConnectionTypeCalculator();
@@ -64,5 +64,5 @@
      * constructor
      */
-    public MemberTableModel(OsmDataLayer layer, PresetHandler presetHandler) {
+    public MemberTableModel(OsmDataLayer layer, TaggingPresetHandler presetHandler) {
         members = new ArrayList<>();
         listeners = new CopyOnWriteArrayList<>();
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java	(revision 8863)
@@ -69,5 +69,5 @@
 import org.openstreetmap.josm.actions.ParameterizedAction;
 import org.openstreetmap.josm.actions.ParameterizedActionDecorator;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java	(revision 8863)
@@ -28,4 +28,5 @@
 import org.openstreetmap.josm.gui.preferences.SourceEditor;
 import org.openstreetmap.josm.gui.preferences.SourceEditor.ExtendedSourceEntry;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
 import org.openstreetmap.josm.gui.preferences.SourceEntry;
 import org.openstreetmap.josm.gui.preferences.SourceProvider;
@@ -33,5 +34,4 @@
 import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
 import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetReader;
 import org.openstreetmap.josm.tools.GBC;
 import org.xml.sax.SAXException;
Index: unk/src/org/openstreetmap/josm/gui/tagging/PresetHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/PresetHandler.java	(revision 8862)
+++ 	(revision )
@@ -1,14 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Tag;
-
-public interface PresetHandler {
-    Collection<OsmPrimitive> getSelection();
-
-    void updateTags(List<Tag> tags);
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/PresetLabel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/PresetLabel.java	(revision 8862)
+++ 	(revision )
@@ -1,73 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import java.awt.Cursor;
-import java.awt.Font;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.font.TextAttribute;
-import java.util.Collections;
-
-import javax.swing.JLabel;
-
-public class PresetLabel extends JLabel {
-
-    protected final TaggingPreset t;
-
-    /**
-     * Constructs a new {@code PresetLabel}.
-     * @param t the tagging preset
-     */
-    public PresetLabel(TaggingPreset t) {
-        super(t.getName() + " …");
-        setIcon(t.getIcon());
-        addMouseListener(new PresetLabelMouseListener(this));
-        this.t = t;
-    }
-
-    /**
-     * Small helper class that manages the highlighting of the label on hover as well as opening
-     * the corresponding preset when clicked
-     */
-    public static class PresetLabelMouseListener implements MouseListener {
-        protected final JLabel label;
-        protected final Font hover;
-        protected final Font normal;
-
-        /**
-         * Constructs a new {@code PresetLabelMouseListener}.
-         * @param lbl Label to highlight
-         */
-        public PresetLabelMouseListener(JLabel lbl) {
-            label = lbl;
-            lbl.setCursor(new Cursor(Cursor.HAND_CURSOR));
-            normal = label.getFont();
-            hover = normal.deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED));
-        }
-
-        @Override
-        public void mouseClicked(MouseEvent e) {
-            // Do nothing
-        }
-
-        @Override
-        public void mouseEntered(MouseEvent e) {
-            label.setFont(hover);
-        }
-
-        @Override
-        public void mouseExited(MouseEvent e) {
-            label.setFont(normal);
-        }
-
-        @Override
-        public void mousePressed(MouseEvent e) {
-            // Do nothing
-        }
-
-        @Override
-        public void mouseReleased(MouseEvent e) {
-            // Do nothing
-        }
-    }
-}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 8863)
@@ -23,4 +23,6 @@
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
@@ -39,5 +41,5 @@
 
     private PresetListPanel presetListPanel;
-    private final transient PresetHandler presetHandler;
+    private final transient TaggingPresetHandler presetHandler;
 
     /**
@@ -140,5 +142,5 @@
      * internally and can be retrieved with {@link #getModel()}.
      */
-    public TagEditorPanel(PresetHandler presetHandler) {
+    public TagEditorPanel(TaggingPresetHandler presetHandler) {
         this(null, presetHandler, 0);
     }
@@ -151,5 +153,5 @@
      * @param maxCharacters maximum number of characters allowed, 0 for unlimited
      */
-    public TagEditorPanel(TagEditorModel model, PresetHandler presetHandler, final int maxCharacters) {
+    public TagEditorPanel(TagEditorModel model, TaggingPresetHandler presetHandler, final int maxCharacters) {
         this.model = model;
         this.presetHandler = presetHandler;
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java	(revision 8862)
+++ 	(revision )
@@ -1,548 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trc;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
-import java.awt.Insets;
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.AbstractAction;
-import javax.swing.Action;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JToggleButton;
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.search.SearchCompiler;
-import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Link;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.PresetLink;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.ImageResource;
-import org.openstreetmap.josm.tools.Predicate;
-import org.openstreetmap.josm.tools.Utils;
-import org.openstreetmap.josm.tools.template_engine.ParseError;
-import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
-import org.openstreetmap.josm.tools.template_engine.TemplateParser;
-import org.xml.sax.SAXException;
-
-/**
- * This class read encapsulate one tagging preset. A class method can
- * read in all predefined presets, either shipped with JOSM or that are
- * in the config directory.
- *
- * It is also able to construct dialogs out of preset definitions.
- * @since 294
- */
-public class TaggingPreset extends AbstractAction implements MapView.LayerChangeListener, Predicate<OsmPrimitive> {
-
-    public static final int DIALOG_ANSWER_APPLY = 1;
-    public static final int DIALOG_ANSWER_NEW_RELATION = 2;
-    public static final int DIALOG_ANSWER_CANCEL = 3;
-
-    public TaggingPresetMenu group;
-    public String name;
-    public String iconName;
-    public String name_context;
-    public String locale_name;
-    public boolean preset_name_label;
-    public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
-
-    /**
-     * The types as preparsed collection.
-     */
-    public Set<TaggingPresetType> types;
-    public transient List<TaggingPresetItem> data = new LinkedList<>();
-    public transient Roles roles;
-    public transient TemplateEntry nameTemplate;
-    public transient Match nameTemplateFilter;
-
-    /**
-     * Create an empty tagging preset. This will not have any items and
-     * will be an empty string as text. createPanel will return null.
-     * Use this as default item for "do not select anything".
-     */
-    public TaggingPreset() {
-        MapView.addLayerChangeListener(this);
-        updateEnabledState();
-    }
-
-    /**
-     * Change the display name without changing the toolbar value.
-     */
-    public void setDisplayName() {
-        putValue(Action.NAME, getName());
-        putValue("toolbar", "tagging_" + getRawName());
-        putValue(OPTIONAL_TOOLTIP_TEXT, group != null ?
-                tr("Use preset ''{0}'' of group ''{1}''", getLocaleName(), group.getName()) :
-                    tr("Use preset ''{0}''", getLocaleName()));
-    }
-
-    public String getLocaleName() {
-        if (locale_name == null) {
-            if (name_context != null) {
-                locale_name = trc(name_context, TaggingPresetItems.fixPresetString(name));
-            } else {
-                locale_name = tr(TaggingPresetItems.fixPresetString(name));
-            }
-        }
-        return locale_name;
-    }
-
-    /**
-     * Returns the translated name of this preset, prefixed with the group names it belongs to.
-     */
-    public String getName() {
-        return group != null ? group.getName() + '/' + getLocaleName() : getLocaleName();
-    }
-
-    /**
-     * Returns the non translated name of this preset, prefixed with the (non translated) group names it belongs to.
-     */
-    public String getRawName() {
-        return group != null ? group.getRawName() + '/' + name : name;
-    }
-
-    /**
-     * Returns the preset icon.
-     * @return The preset icon, or {@code null} if none defined
-     * @since 6403
-     */
-    public final ImageIcon getIcon() {
-        Object icon = getValue(Action.SMALL_ICON);
-        if (icon instanceof ImageIcon) {
-            return (ImageIcon) icon;
-        }
-        return null;
-    }
-
-    /**
-     * Called from the XML parser to set the icon.
-     * The loading task is performed in the background in order to speedup startup.
-     */
-    public void setIcon(final String iconName) {
-        this.iconName = iconName;
-        if (!TaggingPresetReader.isLoadIcons()) {
-            return;
-        }
-        File arch = TaggingPresetReader.getZipIcons();
-        final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
-        ImageProvider imgProv = new ImageProvider(iconName);
-        imgProv.setDirs(s);
-        imgProv.setId("presets");
-        imgProv.setArchive(arch);
-        imgProv.setOptional(true);
-        imgProv.getInBackground(new ImageProvider.ImageResourceCallback() {
-            @Override
-            public void finished(final ImageResource result) {
-                if (result != null) {
-                    GuiHelper.runInEDT(new Runnable() {
-                        @Override
-                        public void run() {
-                            result.getImageIcon(TaggingPreset.this);
-                        }
-                    });
-                } else {
-                    Main.warn("Could not get presets icon " + iconName);
-                }
-            }
-        });
-    }
-
-    /**
-     * Called from the XML parser to set the types this preset affects.
-     */
-    public void setType(String types) throws SAXException {
-        this.types = TaggingPresetItems.getType(types);
-    }
-
-    public void setName_template(String pattern) throws SAXException {
-        try {
-            this.nameTemplate = new TemplateParser(pattern).parse();
-        } catch (ParseError e) {
-            Main.error("Error while parsing " + pattern + ": " + e.getMessage());
-            throw new SAXException(e);
-        }
-    }
-
-    public void setName_template_filter(String filter) throws SAXException {
-        try {
-            this.nameTemplateFilter = SearchCompiler.compile(filter);
-        } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
-            Main.error("Error while parsing" + filter + ": " + e.getMessage());
-            throw new SAXException(e);
-        }
-    }
-
-    private static class PresetPanel extends JPanel {
-        private boolean hasElements;
-
-        PresetPanel() {
-            super(new GridBagLayout());
-        }
-    }
-
-    public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
-        if (data == null)
-            return null;
-        PresetPanel p = new PresetPanel();
-        List<Link> l = new LinkedList<>();
-        List<PresetLink> presetLink = new LinkedList<>();
-        if (types != null) {
-            JPanel pp = new JPanel();
-            for (TaggingPresetType t : types) {
-                JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
-                la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
-                pp.add(la);
-            }
-            p.add(pp, GBC.eol());
-        }
-        if (preset_name_label) {
-            TaggingPresetItems.Label.addLabel(p, getIcon(), getName());
-        }
-
-        boolean presetInitiallyMatches = !selected.isEmpty() && Utils.forAll(selected, this);
-        JPanel items = new JPanel(new GridBagLayout());
-        for (TaggingPresetItem i : data) {
-            if (i instanceof Link) {
-                l.add((Link) i);
-                p.hasElements = true;
-            } else if (i instanceof PresetLink) {
-                presetLink.add((PresetLink) i);
-            } else {
-                if (i.addToPanel(items, selected, presetInitiallyMatches)) {
-                    p.hasElements = true;
-                }
-            }
-        }
-        p.add(items, GBC.eol().fill());
-        if (selected.isEmpty() && !supportsRelation()) {
-            GuiHelper.setEnabledRec(items, false);
-        }
-
-        // add PresetLink
-        if (!presetLink.isEmpty()) {
-            p.add(new JLabel(tr("Edit also …")), GBC.eol().insets(0, 8, 0, 0));
-            for (PresetLink link : presetLink) {
-                link.addToPanel(p, selected, presetInitiallyMatches);
-            }
-        }
-
-        // add Link
-        for (Link link : l) {
-            link.addToPanel(p, selected, presetInitiallyMatches);
-        }
-
-        // "Add toolbar button"
-        JToggleButton tb = new JToggleButton(new ToolbarButtonAction());
-        tb.setFocusable(false);
-        p.add(tb, GBC.std(0, 0).anchor(GBC.LINE_END));
-        return p;
-    }
-
-    public boolean isShowable() {
-        for (TaggingPresetItem i : data) {
-            if (!(i instanceof TaggingPresetItems.Optional || i instanceof TaggingPresetItems.Space || i instanceof TaggingPresetItems.Key))
-                return true;
-        }
-        return false;
-    }
-
-    public String suggestRoleForOsmPrimitive(OsmPrimitive osm) {
-        if (roles != null && osm != null) {
-            for (Role i : roles.roles) {
-                if (i.memberExpression != null && i.memberExpression.match(osm)
-                        && (i.types == null || i.types.isEmpty() || i.types.contains(TaggingPresetType.forPrimitive(osm)))) {
-                    return i.key;
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        if (Main.main == null) {
-            return;
-        }
-        DataSet ds = Main.main.getCurrentDataSet();
-        Collection<OsmPrimitive> participants = Collections.emptyList();
-        if (Main.main != null && ds != null) {
-            participants = ds.getSelected();
-        }
-
-        // Display dialog even if no data layer (used by preset-tagging-tester plugin)
-        Collection<OsmPrimitive> sel = createSelection(participants);
-        int answer = showDialog(sel, supportsRelation());
-
-        if (ds == null) {
-            return;
-        }
-
-        if (!sel.isEmpty() && answer == DIALOG_ANSWER_APPLY) {
-            Command cmd = createCommand(sel, getChangedTags());
-            if (cmd != null) {
-                Main.main.undoRedo.add(cmd);
-            }
-        } else if (answer == DIALOG_ANSWER_NEW_RELATION) {
-            final Relation r = new Relation();
-            final Collection<RelationMember> members = new HashSet<>();
-            for (Tag t : getChangedTags()) {
-                r.put(t.getKey(), t.getValue());
-            }
-            for (OsmPrimitive osm : ds.getSelected()) {
-                String role = suggestRoleForOsmPrimitive(osm);
-                RelationMember rm = new RelationMember(role == null ? "" : role, osm);
-                r.addMember(rm);
-                members.add(rm);
-            }
-            SwingUtilities.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    RelationEditor.getEditor(Main.main.getEditLayer(), r, members).setVisible(true);
-                }
-            });
-        }
-        ds.setSelected(ds.getSelected()); // force update
-    }
-
-    private static class PresetDialog extends ExtendedDialog {
-        PresetDialog(Component content, String title, ImageIcon icon, boolean disableApply, boolean showNewRelation) {
-            super(Main.parent, title,
-                    showNewRelation ?
-                            new String[] {tr("Apply Preset"), tr("New relation"), tr("Cancel")} :
-                                new String[] {tr("Apply Preset"), tr("Cancel")},
-                                true);
-            if (icon != null)
-                setIconImage(icon.getImage());
-            contentInsets = new Insets(10, 5, 0, 5);
-            if (showNewRelation) {
-                setButtonIcons(new String[] {"ok", "dialogs/addrelation", "cancel" });
-            } else {
-                setButtonIcons(new String[] {"ok", "cancel" });
-            }
-            setContent(content);
-            setDefaultButton(1);
-            setupDialog();
-            buttons.get(0).setEnabled(!disableApply);
-            buttons.get(0).setToolTipText(title);
-            // Prevent dialogs of being too narrow (fix #6261)
-            Dimension d = getSize();
-            if (d.width < 350) {
-                d.width = 350;
-                setSize(d);
-            }
-            showDialog();
-        }
-    }
-
-    public int showDialog(Collection<OsmPrimitive> sel, boolean showNewRelation) {
-        PresetPanel p = createPanel(sel);
-        if (p == null)
-            return DIALOG_ANSWER_CANCEL;
-
-        int answer = 1;
-        boolean canCreateRelation = types == null || types.contains(TaggingPresetType.RELATION);
-        if (originalSelectionEmpty && !canCreateRelation) {
-            new Notification(
-                    tr("The preset <i>{0}</i> cannot be applied since nothing has been selected!", getLocaleName()))
-                    .setIcon(JOptionPane.WARNING_MESSAGE)
-                    .show();
-            return DIALOG_ANSWER_CANCEL;
-        } else if (sel.isEmpty() && !canCreateRelation) {
-            new Notification(
-                    tr("The preset <i>{0}</i> cannot be applied since the selection is unsuitable!", getLocaleName()))
-                    .setIcon(JOptionPane.WARNING_MESSAGE)
-                    .show();
-            return DIALOG_ANSWER_CANCEL;
-        } else if (p.getComponentCount() != 0 && (sel.isEmpty() || p.hasElements)) {
-            String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
-            if (sel.isEmpty()) {
-                if (originalSelectionEmpty) {
-                    title = tr("Nothing selected!");
-                } else {
-                    title = tr("Selection unsuitable!");
-                }
-            }
-
-            answer = new PresetDialog(p, title, preset_name_label ? null : (ImageIcon) getValue(Action.SMALL_ICON),
-                    sel.isEmpty(), showNewRelation).getValue();
-        }
-        if (!showNewRelation && answer == 2)
-            return DIALOG_ANSWER_CANCEL;
-        else
-            return answer;
-    }
-
-    /**
-     * True whenever the original selection given into createSelection was empty
-     */
-    private boolean originalSelectionEmpty;
-
-    /**
-     * Removes all unsuitable OsmPrimitives from the given list
-     * @param participants List of possible OsmPrimitives to tag
-     * @return Cleaned list with suitable OsmPrimitives only
-     */
-    public Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) {
-        originalSelectionEmpty = participants.isEmpty();
-        Collection<OsmPrimitive> sel = new LinkedList<>();
-        for (OsmPrimitive osm : participants) {
-            if (typeMatches(EnumSet.of(TaggingPresetType.forPrimitive(osm)))) {
-                sel.add(osm);
-            }
-        }
-        return sel;
-    }
-
-    public List<Tag> getChangedTags() {
-        List<Tag> result = new ArrayList<>();
-        for (TaggingPresetItem i: data) {
-            i.addCommands(result);
-        }
-        return result;
-    }
-
-    public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) {
-        List<Command> cmds = new ArrayList<>();
-        for (Tag tag: changedTags) {
-            cmds.add(new ChangePropertyCommand(sel, tag.getKey(), tag.getValue()));
-        }
-
-        if (cmds.isEmpty())
-            return null;
-        else if (cmds.size() == 1)
-            return cmds.get(0);
-        else
-            return new SequenceCommand(tr("Change Tags"), cmds);
-    }
-
-    private boolean supportsRelation() {
-        return types == null || types.contains(TaggingPresetType.RELATION);
-    }
-
-    protected final void updateEnabledState() {
-        setEnabled(Main.main != null && Main.main.getCurrentDataSet() != null);
-    }
-
-    @Override
-    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
-        updateEnabledState();
-    }
-
-    @Override
-    public void layerAdded(Layer newLayer) {
-        updateEnabledState();
-    }
-
-    @Override
-    public void layerRemoved(Layer oldLayer) {
-        updateEnabledState();
-    }
-
-    @Override
-    public String toString() {
-        return (types == null ? "" : types) + " " + name;
-    }
-
-    public boolean typeMatches(Collection<TaggingPresetType> t) {
-        return t == null || types == null || types.containsAll(t);
-    }
-
-    @Override
-    public boolean evaluate(OsmPrimitive p) {
-        return matches(EnumSet.of(TaggingPresetType.forPrimitive(p)), p.getKeys(), false);
-    }
-
-    public boolean matches(Collection<TaggingPresetType> t, Map<String, String> tags, boolean onlyShowable) {
-        if (onlyShowable && !isShowable())
-            return false;
-        else if (!typeMatches(t))
-            return false;
-        boolean atLeastOnePositiveMatch = false;
-        for (TaggingPresetItem item : data) {
-            Boolean m = item.matches(tags);
-            if (m != null && !m)
-                return false;
-            else if (m != null) {
-                atLeastOnePositiveMatch = true;
-            }
-        }
-        return atLeastOnePositiveMatch;
-    }
-
-    public static Collection<TaggingPreset> getMatchingPresets(final Collection<TaggingPresetType> t,
-            final Map<String, String> tags, final boolean onlyShowable) {
-        return Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() {
-            @Override
-            public boolean evaluate(TaggingPreset object) {
-                return object.matches(t, tags, onlyShowable);
-            }
-        });
-    }
-
-    /**
-     * Action that adds or removes the button on main toolbar
-     */
-    public class ToolbarButtonAction extends AbstractAction {
-        private final int toolbarIndex;
-
-        /**
-         * Constructs a new {@code ToolbarButtonAction}.
-         */
-        public ToolbarButtonAction() {
-            super("", ImageProvider.get("dialogs", "pin"));
-            putValue(SHORT_DESCRIPTION, tr("Add or remove toolbar button"));
-            List<String> t = new LinkedList<>(ToolbarPreferences.getToolString());
-            toolbarIndex = t.indexOf(getToolbarString());
-            putValue(SELECTED_KEY, toolbarIndex >= 0);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent ae) {
-            String res = getToolbarString();
-            Main.toolbar.addCustomButton(res, toolbarIndex, true);
-        }
-    }
-
-    public String getToolbarString() {
-        ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null);
-        return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(this));
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItem.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItem.java	(revision 8862)
+++ 	(revision )
@@ -1,70 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import javax.swing.JPanel;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
-
-/**
- * Class that represents single part of a preset - one field or text label that is shown to user
- * @since 6068
- */
-public abstract class TaggingPresetItem {
-
-    protected void initAutoCompletionField(AutoCompletingTextField field, String... key) {
-        initAutoCompletionField(field, Arrays.asList(key));
-    }
-
-    protected void initAutoCompletionField(AutoCompletingTextField field, List<String> keys) {
-        if (Main.main == null) return;
-        OsmDataLayer layer = Main.main.getEditLayer();
-        if (layer == null) {
-            return;
-        }
-        AutoCompletionList list = new AutoCompletionList();
-        layer.data.getAutoCompletionManager().populateWithTagValues(list, keys);
-        field.setAutoCompletionList(list);
-    }
-
-    /**
-     * Called by {@link TaggingPreset#createPanel} during tagging preset panel creation.
-     * All components defining this tagging preset item must be added to given panel.
-     *
-     * @param p The panel where components must be added
-     * @param sel The related selected OSM primitives
-     * @param presetInitiallyMatches Whether this {@link TaggingPreset} already matched before applying,
-     *                               i.e. whether the map feature already existed on the primitive.
-     * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise.
-     */
-    abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches);
-
-    /**
-     * Adds the new tags to apply to selected OSM primitives when the preset holding this item is applied.
-     * @param changedTags The list of changed tags to modify if needed
-     */
-    abstract void addCommands(List<Tag> changedTags);
-
-    boolean requestFocusInWindow() {
-        return false;
-    }
-
-    /**
-     * Tests whether the tags match this item.
-     * Note that for a match, at least one positive and no negative is required.
-     * @param tags the tags of an {@link OsmPrimitive}
-     * @return {@code true} if matches (positive), {@code null} if neutral, {@code false} if mismatches (negative).
-     */
-    Boolean matches(Map<String, String> tags) {
-        return null;
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java	(revision 8862)
+++ 	(revision )
@@ -1,1597 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trc;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.GridBagLayout;
-import java.awt.GridLayout;
-import java.awt.Insets;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.io.File;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.swing.AbstractButton;
-import javax.swing.BorderFactory;
-import javax.swing.ButtonGroup;
-import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
-import javax.swing.JToggleButton;
-import javax.swing.ListCellRenderer;
-import javax.swing.ListModel;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.search.SearchAction;
-import org.openstreetmap.josm.actions.search.SearchCompiler;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmUtils;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPriority;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
-import org.openstreetmap.josm.gui.widgets.JosmComboBox;
-import org.openstreetmap.josm.gui.widgets.JosmTextField;
-import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
-import org.openstreetmap.josm.gui.widgets.UrlLabel;
-import org.openstreetmap.josm.tools.AlphanumComparator;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Predicate;
-import org.openstreetmap.josm.tools.Utils;
-import org.xml.sax.SAXException;
-
-/**
- * Class that contains all subtypes of TaggingPresetItem, static supplementary data, types and methods
- * @since 6068
- */
-public final class TaggingPresetItems {
-    private TaggingPresetItems() {
-    }
-
-    private static int auto_increment_selected;
-    /** Translatation of "&lt;different&gt;". Use in combo boxes to display en entry matching several different values. */
-    public static final String DIFFERENT = tr("<different>");
-
-    private static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
-
-    // cache the parsing of types using a LRU cache (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
-    private static final Map<String, Set<TaggingPresetType>> TYPE_CACHE = new LinkedHashMap<>(16, 1.1f, true);
-
-    /**
-     * Last value of each key used in presets, used for prefilling corresponding fields
-     */
-    private static final Map<String, String> LAST_VALUES = new HashMap<>();
-
-    public static class PresetListEntry implements Comparable<PresetListEntry> {
-        public String value;
-        /** The context used for translating {@link #value} */
-        public String value_context;
-        public String display_value;
-        public String short_description;
-        /** The location of icon file to display */
-        public String icon;
-        /** The size of displayed icon. If not set, default is size from icon file */
-        public String icon_size;
-        /** The localized version of {@link #display_value}. */
-        public String locale_display_value;
-        /** The localized version of {@link #short_description}. */
-        public String locale_short_description;
-        private final File zipIcons = TaggingPresetReader.getZipIcons();
-
-        // Cached size (currently only for Combo) to speed up preset dialog initialization
-        private int prefferedWidth = -1;
-        private int prefferedHeight = -1;
-
-        public String getListDisplay() {
-            if (value.equals(DIFFERENT))
-                return "<b>"+DIFFERENT.replaceAll("<", "&lt;").replaceAll(">", "&gt;")+"</b>";
-
-            if (value.isEmpty())
-                return "&nbsp;";
-
-            final StringBuilder res = new StringBuilder("<b>");
-            res.append(getDisplayValue(true).replaceAll("<", "&lt;").replaceAll(">", "&gt;"))
-               .append("</b>");
-            if (getShortDescription(true) != null) {
-                // wrap in table to restrict the text width
-                res.append("<div style=\"width:300px; padding:0 0 5px 5px\">")
-                   .append(getShortDescription(true))
-                   .append("</div>");
-            }
-            return res.toString();
-        }
-
-        /**
-         * Returns the entry icon, if any.
-         * @return the entry icon, or {@code null}
-         */
-        public ImageIcon getIcon() {
-            return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size));
-        }
-
-        /**
-         * Constructs a new {@code PresetListEntry}, uninitialized.
-         */
-        public PresetListEntry() {
-        }
-
-        public PresetListEntry(String value) {
-            this.value = value;
-        }
-
-        public String getDisplayValue(boolean translated) {
-            return translated
-                    ? Utils.firstNonNull(locale_display_value, tr(display_value), trc(value_context, value))
-                            : Utils.firstNonNull(display_value, value);
-        }
-
-        public String getShortDescription(boolean translated) {
-            return translated
-                    ? Utils.firstNonNull(locale_short_description, tr(short_description))
-                            : short_description;
-        }
-
-        // toString is mainly used to initialize the Editor
-        @Override
-        public String toString() {
-            if (value.equals(DIFFERENT))
-                return DIFFERENT;
-            return getDisplayValue(true).replaceAll("<.*>", ""); // remove additional markup, e.g. <br>
-        }
-
-        @Override
-        public int compareTo(PresetListEntry o) {
-            return AlphanumComparator.getInstance().compare(this.getDisplayValue(true), o.getDisplayValue(true));
-        }
-    }
-
-    public static class Role {
-        public Set<TaggingPresetType> types;
-        public String key;
-        /** The text to display */
-        public String text;
-        /** The context used for translating {@link #text} */
-        public String text_context;
-        /** The localized version of {@link #text}. */
-        public String locale_text;
-        public SearchCompiler.Match memberExpression;
-
-        public boolean required;
-        private long count;
-
-        public void setType(String types) throws SAXException {
-            this.types = getType(types);
-        }
-
-        public void setRequisite(String str) throws SAXException {
-            if ("required".equals(str)) {
-                required = true;
-            } else if (!"optional".equals(str))
-                throw new SAXException(tr("Unknown requisite: {0}", str));
-        }
-
-        public void setMember_expression(String member_expression) throws SAXException {
-            try {
-                final SearchAction.SearchSetting searchSetting = new SearchAction.SearchSetting();
-                searchSetting.text = member_expression;
-                searchSetting.caseSensitive = true;
-                searchSetting.regexSearch = true;
-                this.memberExpression = SearchCompiler.compile(searchSetting);
-            } catch (SearchCompiler.ParseError ex) {
-                throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
-            }
-        }
-
-        public void setCount(String count) {
-            this.count = Long.parseLong(count);
-        }
-
-        /**
-         * Return either argument, the highest possible value or the lowest allowed value
-         */
-        public long getValidCount(long c) {
-            if (count > 0 && !required)
-                return c != 0 ? count : 0;
-            else if (count > 0)
-                return count;
-            else if (!required)
-                return c != 0 ? c : 0;
-            else
-                return c != 0 ? c : 1;
-        }
-
-        public boolean addToPanel(JPanel p) {
-            String cstring;
-            if (count > 0 && !required) {
-                cstring = "0,"+count;
-            } else if (count > 0) {
-                cstring = String.valueOf(count);
-            } else if (!required) {
-                cstring = "0-...";
-            } else {
-                cstring = "1-...";
-            }
-            if (locale_text == null) {
-                locale_text = getLocaleText(text, text_context, null);
-            }
-            p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));
-            p.add(new JLabel(key), GBC.std().insets(0, 0, 10, 0));
-            p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0, 0, 10, 0));
-            if (types != null) {
-                JPanel pp = new JPanel();
-                for (TaggingPresetType t : types) {
-                    pp.add(new JLabel(ImageProvider.get(t.getIconName())));
-                }
-                p.add(pp, GBC.eol());
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
-     */
-    public enum MatchType {
-
-        /** Neutral, i.e., do not consider this item for matching. */
-        NONE("none"),
-        /** Positive if key matches, neutral otherwise. */
-        KEY("key"),
-        /** Positive if key matches, negative otherwise. */
-        KEY_REQUIRED("key!"),
-        /** Positive if key and value matches, neutral otherwise. */
-        KEY_VALUE("keyvalue"),
-        /** Positive if key and value matches, negative otherwise. */
-        KEY_VALUE_REQUIRED("keyvalue!");
-
-        private final String value;
-
-        MatchType(String value) {
-            this.value = value;
-        }
-
-        /**
-         * Replies the associated textual value.
-         * @return the associated textual value
-         */
-        public String getValue() {
-            return value;
-        }
-
-        /**
-         * Determines the {@code MatchType} for the given textual value.
-         * @param type the textual value
-         * @return the {@code MatchType} for the given textual value
-         */
-        public static MatchType ofString(String type) {
-            for (MatchType i : EnumSet.allOf(MatchType.class)) {
-                if (i.getValue().equals(type))
-                    return i;
-            }
-            throw new IllegalArgumentException(type + " is not allowed");
-        }
-    }
-
-    public static class Usage {
-        private SortedSet<String> values;
-        private boolean hadKeys;
-        private boolean hadEmpty;
-
-        public boolean hasUniqueValue() {
-            return values.size() == 1 && !hadEmpty;
-        }
-
-        public boolean unused() {
-            return values.isEmpty();
-        }
-
-        public String getFirst() {
-            return values.first();
-        }
-
-        public boolean hadKeys() {
-            return hadKeys;
-        }
-    }
-
-    /**
-     * A tagging preset item displaying a localizable text.
-     * @since 6190
-     */
-    public abstract static class TaggingPresetTextItem extends TaggingPresetItem {
-
-        /** The text to display */
-        public String text;
-
-        /** The context used for translating {@link #text} */
-        public String text_context;
-
-        /** The localized version of {@link #text} */
-        public String locale_text;
-
-        protected final void initializeLocaleText(String defaultText) {
-            if (locale_text == null) {
-                locale_text = getLocaleText(text, text_context, defaultText);
-            }
-        }
-
-        @Override
-        void addCommands(List<Tag> changedTags) {
-        }
-
-        protected String fieldsToString() {
-            return (text != null ? "text=" + text + ", " : "")
-                    + (text_context != null ? "text_context=" + text_context + ", " : "")
-                    + (locale_text != null ? "locale_text=" + locale_text : "");
-        }
-
-        @Override
-        public String toString() {
-            return getClass().getSimpleName() + " [" + fieldsToString() + ']';
-        }
-    }
-
-    /**
-     * Label type.
-     */
-    public static class Label extends TaggingPresetTextItem {
-
-        /** The location of icon file to display (optional) */
-        public String icon;
-        /** The size of displayed icon. If not set, default is 16px */
-        public String icon_size;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            initializeLocaleText(null);
-            addLabel(p, getIcon(), locale_text);
-            return true;
-        }
-
-        /**
-         * Adds a new {@code JLabel} to the given panel.
-         * @param p The panel
-         * @param icon the icon (optional, can be null)
-         * @param label The text label
-         */
-        public static void addLabel(JPanel p, Icon icon, String label) {
-            p.add(new JLabel(label, icon, JLabel.LEADING), GBC.eol().fill(GBC.HORIZONTAL));
-        }
-
-        /**
-         * Returns the label icon, if any.
-         * @return the label icon, or {@code null}
-         */
-        public ImageIcon getIcon() {
-            Integer size = parseInteger(icon_size);
-            return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), size != null ? size : 16);
-        }
-    }
-
-    /**
-     * Hyperlink type.
-     */
-    public static class Link extends TaggingPresetTextItem {
-
-        /** The link to display. */
-        public String href;
-
-        /** The localized version of {@link #href}. */
-        public String locale_href;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            initializeLocaleText(tr("More information about this feature"));
-            String url = locale_href;
-            if (url == null) {
-                url = href;
-            }
-            if (url != null) {
-                p.add(new UrlLabel(url, locale_text, 2), GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL));
-            }
-            return false;
-        }
-
-        @Override
-        protected String fieldsToString() {
-            return super.fieldsToString()
-                    + (href != null ? "href=" + href + ", " : "")
-                    + (locale_href != null ? "locale_href=" + locale_href + ", " : "");
-        }
-    }
-
-    public static class PresetLink extends TaggingPresetItem {
-
-        public String preset_name = "";
-
-        @Override
-        boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            final String presetName = preset_name;
-            final TaggingPreset t = Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() {
-                @Override
-                public boolean evaluate(TaggingPreset object) {
-                    return presetName.equals(object.name);
-                }
-            }).iterator().next();
-            if (t == null) return false;
-            JLabel lbl = new PresetLabel(t);
-            lbl.addMouseListener(new MouseAdapter() {
-                @Override
-                public void mouseClicked(MouseEvent arg0) {
-                    t.actionPerformed(null);
-                }
-            });
-            p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));
-            return false;
-        }
-
-        @Override
-        void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Roles extends TaggingPresetItem {
-
-        public final List<Role> roles = new LinkedList<>();
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            p.add(new JLabel(" "), GBC.eol()); // space
-            if (!roles.isEmpty()) {
-                JPanel proles = new JPanel(new GridBagLayout());
-                proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));
-                proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));
-                proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));
-                proles.add(new JLabel(tr("elements")), GBC.eol());
-                for (Role i : roles) {
-                    i.addToPanel(proles);
-                }
-                p.add(proles, GBC.eol());
-            }
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Optional extends TaggingPresetTextItem {
-
-        // TODO: Draw a box around optional stuff
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            initializeLocaleText(tr("Optional Attributes:"));
-            p.add(new JLabel(" "), GBC.eol()); // space
-            p.add(new JLabel(locale_text), GBC.eol());
-            p.add(new JLabel(" "), GBC.eol()); // space
-            return false;
-        }
-    }
-
-    /**
-     * Horizontal separator type.
-     */
-    public static class Space extends TaggingPresetItem {
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            p.add(new JLabel(" "), GBC.eol()); // space
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-
-        @Override
-        public String toString() {
-            return "Space";
-        }
-    }
-
-    /**
-     * Class used to represent a {@link JSeparator} inside tagging preset window.
-     * @since 6198
-     */
-    public static class ItemSeparator extends TaggingPresetItem {
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-
-        @Override
-        public String toString() {
-            return "ItemSeparator";
-        }
-    }
-
-    /**
-     * Preset item associated to an OSM key.
-     */
-    public abstract static class KeyedItem extends TaggingPresetItem {
-
-        public String key;
-        /** The text to display */
-        public String text;
-        /** The context used for translating {@link #text} */
-        public String text_context;
-        public String match = getDefaultMatch().getValue();
-
-        public abstract MatchType getDefaultMatch();
-
-        public abstract Collection<String> getValues();
-
-        @Override
-        Boolean matches(Map<String, String> tags) {
-            switch (MatchType.ofString(match)) {
-            case NONE:
-                return null;
-            case KEY:
-                return tags.containsKey(key) ? Boolean.TRUE : null;
-            case KEY_REQUIRED:
-                return tags.containsKey(key);
-            case KEY_VALUE:
-                return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null;
-            case KEY_VALUE_REQUIRED:
-                return tags.containsKey(key) && getValues().contains(tags.get(key));
-            default:
-                throw new IllegalStateException();
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "KeyedItem [key=" + key + ", text=" + text
-                    + ", text_context=" + text_context + ", match=" + match
-                    + ']';
-        }
-    }
-
-    /**
-     * Invisible type allowing to hardcode an OSM key/value from the preset definition.
-     */
-    public static class Key extends KeyedItem {
-
-        /** The hardcoded value for key */
-        public String value;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-            changedTags.add(new Tag(key, value));
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.KEY_VALUE_REQUIRED;
-        }
-
-        @Override
-        public Collection<String> getValues() {
-            return Collections.singleton(value);
-        }
-
-        @Override
-        public String toString() {
-            return "Key [key=" + key + ", value=" + value + ", text=" + text
-                    + ", text_context=" + text_context + ", match=" + match
-                    + ']';
-        }
-    }
-
-    /**
-     * Text field type.
-     */
-    public static class Text extends KeyedItem {
-
-        /** The localized version of {@link #text}. */
-        public String locale_text;
-        public String default_;
-        public String originalValue;
-        public String use_last_as_default = "false";
-        public String auto_increment;
-        public String length;
-        public String alternative_autocomplete_keys;
-
-        private JComponent value;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-
-            // find out if our key is already used in the selection.
-            Usage usage = determineTextUsage(sel, key);
-            AutoCompletingTextField textField = new AutoCompletingTextField();
-            if (alternative_autocomplete_keys != null) {
-                initAutoCompletionField(textField, (key + ',' + alternative_autocomplete_keys).split(","));
-            } else {
-                initAutoCompletionField(textField, key);
-            }
-            if (Main.pref.getBoolean("taggingpreset.display-keys-as-hint", true)) {
-                textField.setHint(key);
-            }
-            if (length != null && !length.isEmpty()) {
-                textField.setMaxChars(Integer.valueOf(length));
-            }
-            if (usage.unused()) {
-                if (auto_increment_selected != 0  && auto_increment != null) {
-                    try {
-                        textField.setText(Integer.toString(Integer.parseInt(LAST_VALUES.get(key)) + auto_increment_selected));
-                    } catch (NumberFormatException ex) {
-                        // Ignore - cannot auto-increment if last was non-numeric
-                        if (Main.isTraceEnabled()) {
-                            Main.trace(ex.getMessage());
-                        }
-                    }
-                } else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
-                    // selected osm primitives are untagged or filling default values feature is enabled
-                    if (!"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
-                        textField.setText(LAST_VALUES.get(key));
-                    } else {
-                        textField.setText(default_);
-                    }
-                } else {
-                    // selected osm primitives are tagged and filling default values feature is disabled
-                    textField.setText("");
-                }
-                value = textField;
-                originalValue = null;
-            } else if (usage.hasUniqueValue()) {
-                // all objects use the same value
-                textField.setText(usage.getFirst());
-                value = textField;
-                originalValue = usage.getFirst();
-            } else {
-                // the objects have different values
-                JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0]));
-                comboBox.setEditable(true);
-                comboBox.setEditor(textField);
-                comboBox.getEditor().setItem(DIFFERENT);
-                value = comboBox;
-                originalValue = DIFFERENT;
-            }
-            if (locale_text == null) {
-                locale_text = getLocaleText(text, text_context, null);
-            }
-
-            // if there's an auto_increment setting, then wrap the text field
-            // into a panel, appending a number of buttons.
-            // auto_increment has a format like -2,-1,1,2
-            // the text box being the first component in the panel is relied
-            // on in a rather ugly fashion further down.
-            if (auto_increment != null) {
-                ButtonGroup bg = new ButtonGroup();
-                JPanel pnl = new JPanel(new GridBagLayout());
-                pnl.add(value, GBC.std().fill(GBC.HORIZONTAL));
-
-                // first, one button for each auto_increment value
-                for (final String ai : auto_increment.split(",")) {
-                    JToggleButton aibutton = new JToggleButton(ai);
-                    aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai));
-                    aibutton.setMargin(new java.awt.Insets(0, 0, 0, 0));
-                    aibutton.setFocusable(false);
-                    saveHorizontalSpace(aibutton);
-                    bg.add(aibutton);
-                    try {
-                        // TODO there must be a better way to parse a number like "+3" than this.
-                        final int buttonvalue = (NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue();
-                        if (auto_increment_selected == buttonvalue) aibutton.setSelected(true);
-                        aibutton.addActionListener(new ActionListener() {
-                            @Override
-                            public void actionPerformed(ActionEvent e) {
-                                auto_increment_selected = buttonvalue;
-                            }
-                        });
-                        pnl.add(aibutton, GBC.std());
-                    } catch (ParseException x) {
-                        Main.error("Cannot parse auto-increment value of '" + ai + "' into an integer");
-                    }
-                }
-
-                // an invisible toggle button for "release" of the button group
-                final JToggleButton clearbutton = new JToggleButton("X");
-                clearbutton.setVisible(false);
-                clearbutton.setFocusable(false);
-                bg.add(clearbutton);
-                // and its visible counterpart. - this mechanism allows us to
-                // have *no* button selected after the X is clicked, instead
-                // of the X remaining selected
-                JButton releasebutton = new JButton("X");
-                releasebutton.setToolTipText(tr("Cancel auto-increment for this field"));
-                releasebutton.setMargin(new java.awt.Insets(0, 0, 0, 0));
-                releasebutton.setFocusable(false);
-                releasebutton.addActionListener(new ActionListener() {
-                    @Override
-                    public void actionPerformed(ActionEvent e) {
-                        auto_increment_selected = 0;
-                        clearbutton.setSelected(true);
-                    }
-                });
-                saveHorizontalSpace(releasebutton);
-                pnl.add(releasebutton, GBC.eol());
-                value = pnl;
-            }
-            p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));
-            p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
-            return true;
-        }
-
-        private static void saveHorizontalSpace(AbstractButton button) {
-            Insets insets = button.getBorder().getBorderInsets(button);
-            // Ensure the current look&feel does not waste horizontal space (as seen in Nimbus & Aqua)
-            if (insets != null && insets.left+insets.right > insets.top+insets.bottom) {
-                int min = Math.min(insets.top, insets.bottom);
-                button.setBorder(BorderFactory.createEmptyBorder(insets.top, min, insets.bottom, min));
-            }
-        }
-
-        private static String getValue(Component comp) {
-            if (comp instanceof JosmComboBox) {
-                return ((JosmComboBox<?>) comp).getEditor().getItem().toString();
-            } else if (comp instanceof JosmTextField) {
-                return ((JosmTextField) comp).getText();
-            } else if (comp instanceof JPanel) {
-                return getValue(((JPanel) comp).getComponent(0));
-            } else {
-                return null;
-            }
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-
-            // return if unchanged
-            String v = getValue(value);
-            if (v == null) {
-                Main.error("No 'last value' support for component " + value);
-                return;
-            }
-
-            v = Tag.removeWhiteSpaces(v);
-
-            if (!"false".equals(use_last_as_default) || auto_increment != null) {
-                LAST_VALUES.put(key, v);
-            }
-            if (v.equals(originalValue) || (originalValue == null && v.isEmpty()))
-                return;
-
-            changedTags.add(new Tag(key, v));
-            AutoCompletionManager.rememberUserInput(key, v, true);
-        }
-
-        @Override
-        boolean requestFocusInWindow() {
-            return value.requestFocusInWindow();
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.NONE;
-        }
-
-        @Override
-        public Collection<String> getValues() {
-            if (default_ == null || default_.isEmpty())
-                return Collections.emptyList();
-            return Collections.singleton(default_);
-        }
-    }
-
-    /**
-     * A group of {@link Check}s.
-     * @since 6114
-     */
-    public static class CheckGroup extends TaggingPresetItem {
-
-        /**
-         * Number of columns (positive integer)
-         */
-        public String columns;
-
-        /**
-         * List of checkboxes
-         */
-        public final List<Check> checks = new LinkedList<>();
-
-        @Override
-        boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-            Integer cols = Integer.valueOf(columns);
-            int rows = (int) Math.ceil(checks.size()/cols.doubleValue());
-            JPanel panel = new JPanel(new GridLayout(rows, cols));
-
-            for (Check check : checks) {
-                check.addToPanel(panel, sel, presetInitiallyMatches);
-            }
-
-            p.add(panel, GBC.eol());
-            return false;
-        }
-
-        @Override
-        void addCommands(List<Tag> changedTags) {
-            for (Check check : checks) {
-                check.addCommands(changedTags);
-            }
-        }
-
-        @Override
-        Boolean matches(Map<String, String> tags) {
-            for (Check check : checks) {
-                if (Boolean.TRUE.equals(check.matches(tags))) {
-                    return Boolean.TRUE;
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "CheckGroup [columns=" + columns + ']';
-        }
-    }
-
-    /**
-     * Checkbox type.
-     */
-    public static class Check extends KeyedItem {
-
-        /** The localized version of {@link #text}. */
-        public String locale_text;
-        /** the value to set when checked (default is "yes") */
-        public String value_on = OsmUtils.trueval;
-        /** the value to set when unchecked (default is "no") */
-        public String value_off = OsmUtils.falseval;
-        /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */
-        public boolean disable_off;
-        /** "on" or "off" or unset (default is unset) */
-        public String default_; // only used for tagless objects
-
-        private QuadStateCheckBox check;
-        private QuadStateCheckBox.State initialState;
-        private Boolean def;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-
-            // find out if our key is already used in the selection.
-            final Usage usage = determineBooleanUsage(sel, key);
-            final String oneValue = usage.values.isEmpty() ? null : usage.values.last();
-            def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null;
-
-            if (locale_text == null) {
-                locale_text = getLocaleText(text, text_context, null);
-            }
-
-            if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
-                if (def != null && !PROP_FILL_DEFAULT.get()) {
-                    // default is set and filling default values feature is disabled - check if all primitives are untagged
-                    for (OsmPrimitive s : sel) {
-                        if (s.hasKeys()) {
-                            def = null;
-                        }
-                    }
-                }
-
-                // all selected objects share the same value which is either true or false or unset,
-                // we can display a standard check box.
-                initialState = value_on.equals(oneValue) || Boolean.TRUE.equals(def)
-                        ? QuadStateCheckBox.State.SELECTED
-                        : value_off.equals(oneValue) || Boolean.FALSE.equals(def)
-                        ? QuadStateCheckBox.State.NOT_SELECTED
-                        : QuadStateCheckBox.State.UNSET;
-
-            } else {
-                def = null;
-                // the objects have different values, or one or more objects have something
-                // else than true/false. we display a quad-state check box
-                // in "partial" state.
-                initialState = QuadStateCheckBox.State.PARTIAL;
-            }
-
-            final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4);
-            if (QuadStateCheckBox.State.PARTIAL.equals(initialState))
-                allowedStates.add(QuadStateCheckBox.State.PARTIAL);
-            allowedStates.add(QuadStateCheckBox.State.SELECTED);
-            if (!disable_off || value_off.equals(oneValue))
-                allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED);
-            allowedStates.add(QuadStateCheckBox.State.UNSET);
-            check = new QuadStateCheckBox(locale_text, initialState,
-                    allowedStates.toArray(new QuadStateCheckBox.State[allowedStates.size()]));
-
-            p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
-            return true;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-            // if the user hasn't changed anything, don't create a command.
-            if (check.getState() == initialState && def == null) return;
-
-            // otherwise change things according to the selected value.
-            changedTags.add(new Tag(key,
-                    check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
-                        check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
-                            null));
-        }
-
-        @Override
-        boolean requestFocusInWindow() {
-            return check.requestFocusInWindow();
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.NONE;
-        }
-
-        @Override
-        public Collection<String> getValues() {
-            return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);
-        }
-
-        @Override
-        public String toString() {
-            return "Check ["
-                    + (locale_text != null ? "locale_text=" + locale_text + ", " : "")
-                    + (value_on != null ? "value_on=" + value_on + ", " : "")
-                    + (value_off != null ? "value_off=" + value_off + ", " : "")
-                    + "default_=" + default_ + ", "
-                    + (check != null ? "check=" + check + ", " : "")
-                    + (initialState != null ? "initialState=" + initialState
-                            + ", " : "") + "def=" + def + ']';
-        }
-    }
-
-    /**
-     * Abstract superclass for combo box and multi-select list types.
-     */
-    public abstract static class ComboMultiSelect extends KeyedItem {
-
-        /** The localized version of {@link #text}. */
-        public String locale_text;
-        public String values;
-        public String values_from;
-        /** The context used for translating {@link #values} */
-        public String values_context;
-        public String display_values;
-        /** The localized version of {@link #display_values}. */
-        public String locale_display_values;
-        public String short_descriptions;
-        /** The localized version of {@link #short_descriptions}. */
-        public String locale_short_descriptions;
-        public String default_;
-        public String delimiter = ";";
-        public String use_last_as_default = "false";
-        /** whether to use values for search via {@link TaggingPresetSelector} */
-        public String values_searchable = "false";
-
-        protected JComponent component;
-        protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<>();
-        private boolean initialized;
-        protected Usage usage;
-        protected Object originalValue;
-
-        protected abstract Object getSelectedItem();
-
-        protected abstract void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches);
-
-        protected char getDelChar() {
-            return delimiter.isEmpty() ? ';' : delimiter.charAt(0);
-        }
-
-        @Override
-        public Collection<String> getValues() {
-            initListEntries();
-            return lhm.keySet();
-        }
-
-        public Collection<String> getDisplayValues() {
-            initListEntries();
-            return Utils.transform(lhm.values(), new Utils.Function<PresetListEntry, String>() {
-                @Override
-                public String apply(PresetListEntry x) {
-                    return x.getDisplayValue(true);
-                }
-            });
-        }
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
-
-            initListEntries();
-
-            // find out if our key is already used in the selection.
-            usage = determineTextUsage(sel, key);
-            if (!usage.hasUniqueValue() && !usage.unused()) {
-                lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT));
-            }
-
-            p.add(new JLabel(tr("{0}:", locale_text)), GBC.std().insets(0, 0, 10, 0));
-            addToPanelAnchor(p, default_, presetInitiallyMatches);
-
-            return true;
-
-        }
-
-        private void initListEntries() {
-            if (initialized) {
-                lhm.remove(DIFFERENT); // possibly added in #addToPanel
-                return;
-            } else if (lhm.isEmpty()) {
-                initListEntriesFromAttributes();
-            } else {
-                if (values != null) {
-                    Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
-                            + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
-                            key, text, "values", "list_entry"));
-                }
-                if (display_values != null || locale_display_values != null) {
-                    Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
-                            + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
-                            key, text, "display_values", "list_entry"));
-                }
-                if (short_descriptions != null || locale_short_descriptions != null) {
-                    Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
-                            + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
-                            key, text, "short_descriptions", "list_entry"));
-                }
-                for (PresetListEntry e : lhm.values()) {
-                    if (e.value_context == null) {
-                        e.value_context = values_context;
-                    }
-                }
-            }
-            if (locale_text == null) {
-                locale_text = getLocaleText(text, text_context, null);
-            }
-            initialized = true;
-        }
-
-        private void initListEntriesFromAttributes() {
-            char delChar = getDelChar();
-
-            String[] value_array = null;
-
-            if (values_from != null) {
-                String[] class_method = values_from.split("#");
-                if (class_method != null && class_method.length == 2) {
-                    try {
-                        Method method = Class.forName(class_method[0]).getMethod(class_method[1]);
-                        // Check method is public static String[] methodName()
-                        int mod = method.getModifiers();
-                        if (Modifier.isPublic(mod) && Modifier.isStatic(mod)
-                                && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) {
-                            value_array = (String[]) method.invoke(null);
-                        } else {
-                            Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,
-                                    "public static String[] methodName()"));
-                        }
-                    } catch (Exception e) {
-                        Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text,
-                                e.getClass().getName(), e.getMessage()));
-                    }
-                }
-            }
-
-            if (value_array == null) {
-                value_array = splitEscaped(delChar, values);
-            }
-
-            final String displ = Utils.firstNonNull(locale_display_values, display_values);
-            String[] display_array = displ == null ? value_array : splitEscaped(delChar, displ);
-
-            final String descr = Utils.firstNonNull(locale_short_descriptions, short_descriptions);
-            String[] short_descriptions_array = descr == null ? null : splitEscaped(delChar, descr);
-
-            if (display_array.length != value_array.length) {
-                Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''",
-                                key, text));
-                display_array = value_array;
-            }
-
-            if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) {
-                Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''",
-                                key, text));
-                short_descriptions_array = null;
-            }
-
-            final List<PresetListEntry> entries = new ArrayList<>(value_array.length);
-            for (int i = 0; i < value_array.length; i++) {
-                final PresetListEntry e = new PresetListEntry(value_array[i]);
-                e.locale_display_value = locale_display_values != null
-                        ? display_array[i]
-                        : trc(values_context, fixPresetString(display_array[i]));
-                if (short_descriptions_array != null) {
-                    e.locale_short_description = locale_short_descriptions != null
-                            ? short_descriptions_array[i]
-                            : tr(fixPresetString(short_descriptions_array[i]));
-                }
-
-                entries.add(e);
-            }
-
-            if (Main.pref.getBoolean("taggingpreset.sortvalues", true)) {
-                Collections.sort(entries);
-            }
-
-            for (PresetListEntry i : entries) {
-                lhm.put(i.value, i);
-            }
-
-        }
-
-        protected String getDisplayIfNull() {
-            return null;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-            Object obj = getSelectedItem();
-            String display = (obj == null) ? null : obj.toString();
-            String value = null;
-            if (display == null) {
-                display = getDisplayIfNull();
-            }
-
-            if (display != null) {
-                for (Entry<String, PresetListEntry> entry : lhm.entrySet()) {
-                    String k = entry.getValue().toString();
-                    if (k != null && k.equals(display)) {
-                        value = entry.getKey();
-                        break;
-                    }
-                }
-                if (value == null) {
-                    value = display;
-                }
-            } else {
-                value = "";
-            }
-            value = Tag.removeWhiteSpaces(value);
-
-            // no change if same as before
-            if (originalValue == null) {
-                if (value.isEmpty())
-                    return;
-            } else if (value.equals(originalValue.toString()))
-                return;
-
-            if (!"false".equals(use_last_as_default)) {
-                LAST_VALUES.put(key, value);
-            }
-            changedTags.add(new Tag(key, value));
-        }
-
-        public void addListEntry(PresetListEntry e) {
-            lhm.put(e.value, e);
-        }
-
-        public void addListEntries(Collection<PresetListEntry> e) {
-            for (PresetListEntry i : e) {
-                addListEntry(i);
-            }
-        }
-
-        @Override
-        boolean requestFocusInWindow() {
-            return component.requestFocusInWindow();
-        }
-
-        private static final ListCellRenderer<PresetListEntry> RENDERER = new ListCellRenderer<PresetListEntry>() {
-
-            private final JLabel lbl = new JLabel();
-
-            @Override
-            public Component getListCellRendererComponent(JList<? extends PresetListEntry> list, PresetListEntry item, int index,
-                    boolean isSelected, boolean cellHasFocus) {
-
-                // Only return cached size, item is not shown
-                if (!list.isShowing() && item.prefferedWidth != -1 && item.prefferedHeight != -1) {
-                    if (index == -1) {
-                        lbl.setPreferredSize(new Dimension(item.prefferedWidth, 10));
-                    } else {
-                        lbl.setPreferredSize(new Dimension(item.prefferedWidth, item.prefferedHeight));
-                    }
-                    return lbl;
-                }
-
-                lbl.setPreferredSize(null);
-
-                if (isSelected) {
-                    lbl.setBackground(list.getSelectionBackground());
-                    lbl.setForeground(list.getSelectionForeground());
-                } 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.prefferedWidth = lbl.getPreferredSize().width;
-                item.prefferedHeight = lbl.getPreferredSize().height;
-
-                // We do not want the editor to have the maximum height of all
-                // entries. Return a dummy with bogus height.
-                if (index == -1) {
-                    lbl.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10));
-                }
-                return lbl;
-            }
-        };
-
-        protected ListCellRenderer<PresetListEntry> getListCellRenderer() {
-            return RENDERER;
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.NONE;
-        }
-    }
-
-    /**
-     * Combobox type.
-     */
-    public static class Combo extends ComboMultiSelect {
-
-        public boolean editable = true;
-        protected JosmComboBox<PresetListEntry> combo;
-        public String length;
-
-        /**
-         * Constructs a new {@code Combo}.
-         */
-        public Combo() {
-            delimiter = ",";
-        }
-
-        @Override
-        protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
-            if (!usage.unused()) {
-                for (String s : usage.values) {
-                    if (!lhm.containsKey(s)) {
-                        lhm.put(s, new PresetListEntry(s));
-                    }
-                }
-            }
-            if (def != null && !lhm.containsKey(def)) {
-                lhm.put(def, new PresetListEntry(def));
-            }
-            lhm.put("", new PresetListEntry(""));
-
-            combo = new JosmComboBox<>(lhm.values().toArray(new PresetListEntry[0]));
-            component = combo;
-            combo.setRenderer(getListCellRenderer());
-            combo.setEditable(editable);
-            combo.reinitialize(lhm.values());
-            AutoCompletingTextField tf = new AutoCompletingTextField();
-            initAutoCompletionField(tf, key);
-            if (Main.pref.getBoolean("taggingpreset.display-keys-as-hint", true)) {
-                tf.setHint(key);
-            }
-            if (length != null && !length.isEmpty()) {
-                tf.setMaxChars(Integer.valueOf(length));
-            }
-            AutoCompletionList acList = tf.getAutoCompletionList();
-            if (acList != null) {
-                acList.add(getDisplayValues(), AutoCompletionItemPriority.IS_IN_STANDARD);
-            }
-            combo.setEditor(tf);
-
-            if (usage.hasUniqueValue()) {
-                // all items have the same value (and there were no unset items)
-                originalValue = lhm.get(usage.getFirst());
-                combo.setSelectedItem(originalValue);
-            } else if (def != null && usage.unused()) {
-                // default is set and all items were unset
-                if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
-                    // selected osm primitives are untagged or filling default feature is enabled
-                    combo.setSelectedItem(lhm.get(def).getDisplayValue(true));
-                } else {
-                    // selected osm primitives are tagged and filling default feature is disabled
-                    combo.setSelectedItem("");
-                }
-                originalValue = lhm.get(DIFFERENT);
-            } else if (usage.unused()) {
-                // all items were unset (and so is default)
-                originalValue = lhm.get("");
-                if ("force".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
-                    combo.setSelectedItem(lhm.get(LAST_VALUES.get(key)));
-                } else {
-                    combo.setSelectedItem(originalValue);
-                }
-            } else {
-                originalValue = lhm.get(DIFFERENT);
-                combo.setSelectedItem(originalValue);
-            }
-            p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
-
-        }
-
-        @Override
-        protected Object getSelectedItem() {
-            return combo.getSelectedItem();
-
-        }
-
-        @Override
-        protected String getDisplayIfNull() {
-            if (combo.isEditable())
-                return combo.getEditor().getItem().toString();
-            else
-                return null;
-        }
-    }
-
-    /**
-     * Multi-select list type.
-     */
-    public static class MultiSelect extends ComboMultiSelect {
-
-        /**
-         * Number of rows to display (positive integer, optional).
-         */
-        public String rows;
-        protected ConcatenatingJList list;
-
-        @Override
-        protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
-            list = new ConcatenatingJList(delimiter, lhm.values().toArray(new PresetListEntry[0]));
-            component = list;
-            ListCellRenderer<PresetListEntry> renderer = getListCellRenderer();
-            list.setCellRenderer(renderer);
-
-            if (usage.hasUniqueValue() && !usage.unused()) {
-                originalValue = usage.getFirst();
-                list.setSelectedItem(originalValue);
-            } else if (def != null && !usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
-                originalValue = DIFFERENT;
-                list.setSelectedItem(def);
-            } else if (usage.unused()) {
-                originalValue = null;
-                list.setSelectedItem(originalValue);
-            } else {
-                originalValue = DIFFERENT;
-                list.setSelectedItem(originalValue);
-            }
-
-            JScrollPane sp = new JScrollPane(list);
-            // if a number of rows has been specified in the preset,
-            // modify preferred height of scroll pane to match that row count.
-            if (rows != null) {
-                double height = renderer.getListCellRendererComponent(list,
-                        new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * Integer.parseInt(rows);
-                sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height));
-            }
-            p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));
-        }
-
-        @Override
-        protected Object getSelectedItem() {
-            return list.getSelectedItem();
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-            // Do not create any commands if list has been disabled because of an unknown value (fix #8605)
-            if (list.isEnabled()) {
-                super.addCommands(changedTags);
-            }
-        }
-    }
-
-    /**
-    * Class that allows list values to be assigned and retrieved as a comma-delimited
-    * string (extracted from TaggingPreset)
-    */
-    private static class ConcatenatingJList extends JList<PresetListEntry> {
-        private String delimiter;
-
-        ConcatenatingJList(String del, PresetListEntry[] o) {
-            super(o);
-            delimiter = del;
-        }
-
-        public void setSelectedItem(Object o) {
-            if (o == null) {
-                clearSelection();
-            } else {
-                String s = o.toString();
-                Set<String> parts = new TreeSet<>(Arrays.asList(s.split(delimiter)));
-                ListModel<PresetListEntry> lm = getModel();
-                int[] intParts = new int[lm.getSize()];
-                int j = 0;
-                for (int i = 0; i < lm.getSize(); i++) {
-                    final String value = lm.getElementAt(i).value;
-                    if (parts.contains(value)) {
-                        intParts[j++] = i;
-                        parts.remove(value);
-                    }
-                }
-                setSelectedIndices(Arrays.copyOf(intParts, j));
-                // check if we have actually managed to represent the full
-                // value with our presets. if not, cop out; we will not offer
-                // a selection list that threatens to ruin the value.
-                setEnabled(parts.isEmpty());
-            }
-        }
-
-        public String getSelectedItem() {
-            ListModel<PresetListEntry> lm = getModel();
-            int[] si = getSelectedIndices();
-            StringBuilder builder = new StringBuilder();
-            for (int i = 0; i < si.length; i++) {
-                if (i > 0) {
-                    builder.append(delimiter);
-                }
-                builder.append(lm.getElementAt(si[i]).value);
-            }
-            return builder.toString();
-        }
-    }
-
-    public static Set<TaggingPresetType> getType(String types) throws SAXException {
-        if (types == null || types.isEmpty()) {
-            throw new SAXException(tr("Unknown type: {0}", types));
-        }
-        if (TYPE_CACHE.containsKey(types))
-            return TYPE_CACHE.get(types);
-        Set<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class);
-        for (String type : Arrays.asList(types.split(","))) {
-            try {
-                TaggingPresetType presetType = TaggingPresetType.fromString(type);
-                result.add(presetType);
-            } catch (IllegalArgumentException e) {
-                throw new SAXException(tr("Unknown type: {0}", type), e);
-            }
-        }
-        TYPE_CACHE.put(types, result);
-        return result;
-    }
-
-    static String fixPresetString(String s) {
-        return s == null ? s : s.replaceAll("'", "''");
-    }
-
-    private static String getLocaleText(String text, String text_context, String defaultText) {
-        if (text == null) {
-            return defaultText;
-        } else if (text_context != null) {
-            return trc(text_context, fixPresetString(text));
-        } else {
-            return tr(fixPresetString(text));
-        }
-    }
-
-    /**
-     * allow escaped comma in comma separated list:
-     * "A\, B\, C,one\, two" --&gt; ["A, B, C", "one, two"]
-     * @param delimiter the delimiter, e.g. a comma. separates the entries and
-     *      must be escaped within one entry
-     * @param s the string
-     */
-    private static String[] splitEscaped(char delimiter, String s) {
-        if (s == null)
-            return new String[0];
-        List<String> result = new ArrayList<>();
-        boolean backslash = false;
-        StringBuilder item = new StringBuilder();
-        for (int i = 0; i < s.length(); i++) {
-            char ch = s.charAt(i);
-            if (backslash) {
-                item.append(ch);
-                backslash = false;
-            } else if (ch == '\\') {
-                backslash = true;
-            } else if (ch == delimiter) {
-                result.add(item.toString());
-                item.setLength(0);
-            } else {
-                item.append(ch);
-            }
-        }
-        if (item.length() > 0) {
-            result.add(item.toString());
-        }
-        return result.toArray(new String[result.size()]);
-    }
-
-    static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
-        Usage returnValue = new Usage();
-        returnValue.values = new TreeSet<>();
-        for (OsmPrimitive s : sel) {
-            String v = s.get(key);
-            if (v != null) {
-                returnValue.values.add(v);
-            } else {
-                returnValue.hadEmpty = true;
-            }
-            if (s.hasKeys()) {
-                returnValue.hadKeys = true;
-            }
-        }
-        return returnValue;
-    }
-
-    static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
-
-        Usage returnValue = new Usage();
-        returnValue.values = new TreeSet<>();
-        for (OsmPrimitive s : sel) {
-            String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
-            if (booleanValue != null) {
-                returnValue.values.add(booleanValue);
-            }
-        }
-        return returnValue;
-    }
-
-    protected static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) {
-        final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
-        ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true);
-        if (maxSize != null) {
-            imgProv.setMaxSize(maxSize);
-        }
-        return imgProv.get();
-    }
-
-    protected static Integer parseInteger(String str) {
-        if (str == null || str.isEmpty())
-            return null;
-        try {
-            return Integer.valueOf(str);
-        } catch (Exception e) {
-            if (Main.isTraceEnabled()) {
-                Main.trace(e.getMessage());
-            }
-        }
-        return null;
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetListener.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetListener.java	(revision 8862)
+++ 	(revision )
@@ -1,14 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-/**
- * Notification of tagging presets events.
- * @since 7100
- */
-public interface TaggingPresetListener {
-
-    /**
-     * Called after list of tagging presets has been modified.
-     */
-    void taggingPresetsModified();
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetMenu.java	(revision 8862)
+++ 	(revision )
@@ -1,157 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.awt.MouseInfo;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-import javax.swing.Action;
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-import javax.swing.JSeparator;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.tools.AlphanumComparator;
-
-public class TaggingPresetMenu extends TaggingPreset {
-    public JMenu menu; // set by TaggingPresets
-
-    private static class PresetTextComparator implements Comparator<JMenuItem>, Serializable {
-        @Override
-        public int compare(JMenuItem o1, JMenuItem o2) {
-            if (Main.main.menu.presetSearchAction.equals(o1.getAction()))
-                return -1;
-            else if (Main.main.menu.presetSearchAction.equals(o2.getAction()))
-                return 1;
-            else
-                return AlphanumComparator.getInstance().compare(o1.getText(), o2.getText());
-        }
-    }
-
-    /**
-     * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match.
-     */
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        TaggingPresetMenu that = (TaggingPresetMenu) o;
-        return Objects.equals(getRawName(), that.getRawName());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getRawName());
-    }
-
-    @Override
-    public void setDisplayName() {
-        putValue(Action.NAME, getName());
-        /** Tooltips should be shown for the toolbar buttons, but not in the menu. */
-        putValue(OPTIONAL_TOOLTIP_TEXT, group != null ?
-                tr("Preset group {1} / {0}", getLocaleName(), group.getName()) :
-                    tr("Preset group {0}", getLocaleName()));
-        putValue("toolbar", "tagginggroup_" + getRawName());
-    }
-
-    private Component copyMenuComponent(Component menuComponent) {
-        if (menuComponent instanceof JMenu) {
-            JMenu menu = (JMenu) menuComponent;
-            JMenu result = new JMenu(menu.getAction());
-            for (Component item:menu.getMenuComponents()) {
-                result.add(copyMenuComponent(item));
-            }
-            result.setText(menu.getText());
-            return result;
-        } else if (menuComponent instanceof JMenuItem) {
-            JMenuItem menuItem = (JMenuItem) menuComponent;
-            JMenuItem result = new JMenuItem(menuItem.getAction());
-            result.setText(menuItem.getText());
-            return result;
-        } else if (menuComponent instanceof JSeparator) {
-            return new JSeparator();
-        } else {
-            return menuComponent;
-        }
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        Object s = e.getSource();
-        if (menu != null && s instanceof Component) {
-            JPopupMenu pm = new JPopupMenu(getName());
-            for (Component c : menu.getMenuComponents()) {
-                pm.add(copyMenuComponent(c));
-            }
-            Point p = MouseInfo.getPointerInfo().getLocation();
-            pm.show(Main.parent, p.x-Main.parent.getX(), p.y-Main.parent.getY());
-        }
-    }
-
-    /**
-     * Sorts the menu items using the translated item text
-     */
-    public void sortMenu() {
-        TaggingPresetMenu.sortMenu(this.menu);
-    }
-
-    /**
-     * Sorts the menu items using the translated item text
-     */
-    public static void sortMenu(JMenu menu) {
-        Component[] items = menu.getMenuComponents();
-        PresetTextComparator comp = new PresetTextComparator();
-        List<JMenuItem> sortarray = new ArrayList<>();
-        int lastSeparator = 0;
-        for (int i = 0; i < items.length; i++) {
-            Object item = items[i];
-            if (item instanceof JMenu) {
-                sortMenu((JMenu) item);
-            }
-            if (item instanceof JMenuItem) {
-                sortarray.add((JMenuItem) item);
-                if (i == items.length-1) {
-                    Collections.sort(sortarray, comp);
-                    int pos = 0;
-                    for (JMenuItem menuItem : sortarray) {
-                        int oldPos;
-                        if (lastSeparator == 0) {
-                            oldPos = pos;
-                        } else {
-                            oldPos = pos+lastSeparator+1;
-                        }
-                        menu.add(menuItem, oldPos);
-                        pos++;
-                    }
-                    sortarray = new ArrayList<>();
-                    lastSeparator = 0;
-                }
-            } else if (item instanceof JSeparator) {
-                Collections.sort(sortarray, comp);
-                int pos = 0;
-                for (JMenuItem menuItem : sortarray) {
-                    int oldPos;
-                    if (lastSeparator == 0) {
-                        oldPos = pos;
-                    } else {
-                        oldPos = pos+lastSeparator+1;
-                    }
-                    menu.add(menuItem, oldPos);
-                    pos++;
-                }
-                sortarray = new ArrayList<>();
-                lastSeparator = i;
-            }
-        }
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetNameTemplateList.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetNameTemplateList.java	(revision 8862)
+++ 	(revision )
@@ -1,78 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-
-/**
- * List of tagging presets with name templates, allows to find appropriate template based on existing primitive
- */
-public final class TaggingPresetNameTemplateList implements TaggingPresetListener {
-
-    private static TaggingPresetNameTemplateList instance;
-
-    /**
-     * Replies the unique instance.
-     * @return the unique instance
-     */
-    public static synchronized TaggingPresetNameTemplateList getInstance() {
-        if (instance == null) {
-            instance = new TaggingPresetNameTemplateList();
-            TaggingPresets.addListener(instance);
-        }
-        return instance;
-    }
-
-    private final List<TaggingPreset> presetsWithPattern = new LinkedList<>();
-
-    private TaggingPresetNameTemplateList() {
-        buildPresetsWithPattern();
-    }
-
-    private void buildPresetsWithPattern() {
-        synchronized (this) {
-            Main.debug("Building list of presets with name template");
-            presetsWithPattern.clear();
-            for (TaggingPreset tp : TaggingPresets.getTaggingPresets()) {
-                if (tp.nameTemplate != null) {
-                    presetsWithPattern.add(tp);
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds and returns the first occurence of preset with template name matching the given primitive
-     * @param primitive The primitive to match
-     * @return the first occurence of preset with template name matching the primitive
-     */
-    public TaggingPreset findPresetTemplate(OsmPrimitive primitive) {
-        synchronized (this) {
-            for (TaggingPreset t : presetsWithPattern) {
-                Collection<TaggingPresetType> type = Collections.singleton(TaggingPresetType.forPrimitive(primitive));
-                if (t.typeMatches(type)) {
-                    if (t.nameTemplateFilter != null) {
-                        if (t.nameTemplateFilter.match(primitive))
-                            return t;
-                        else {
-                            continue;
-                        }
-                    } else if (t.matches(type, primitive.getKeys(), false)) {
-                        return t;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void taggingPresetsModified() {
-        buildPresetsWithPattern();
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java	(revision 8862)
+++ 	(revision )
@@ -1,405 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
-import org.openstreetmap.josm.io.CachedFile;
-import org.openstreetmap.josm.io.UTFInputStreamReader;
-import org.openstreetmap.josm.tools.Predicates;
-import org.openstreetmap.josm.tools.Utils;
-import org.openstreetmap.josm.tools.XmlObjectParser;
-import org.xml.sax.SAXException;
-
-/**
- * The tagging presets reader.
- * @since 6068
- */
-public final class TaggingPresetReader {
-
-    /**
-     * The accepted MIME types sent in the HTTP Accept header.
-     * @since 6867
-     */
-    public static final String PRESET_MIME_TYPES =
-            "application/xml, text/xml, text/plain; q=0.8, application/zip, application/octet-stream; q=0.5";
-
-    private TaggingPresetReader() {
-        // Hide default constructor for utils classes
-    }
-
-    private static volatile File zipIcons;
-    private static volatile boolean loadIcons = true;
-
-    /**
-     * Returns the set of preset source URLs.
-     * @return The set of preset source URLs.
-     */
-    public static Set<String> getPresetSources() {
-        return new TaggingPresetPreference.PresetPrefHelper().getActiveUrls();
-    }
-
-    /**
-     * Holds a reference to a chunk of items/objects.
-     */
-    public static class Chunk {
-        /** The chunk id, can be referenced later */
-        public String id;
-    }
-
-    /**
-     * Holds a reference to an earlier item/object.
-     */
-    public static class Reference {
-        /** Reference matching a chunk id defined earlier **/
-        public String ref;
-    }
-
-    private static XmlObjectParser buildParser() {
-        XmlObjectParser parser = new XmlObjectParser();
-        parser.mapOnStart("item", TaggingPreset.class);
-        parser.mapOnStart("separator", TaggingPresetSeparator.class);
-        parser.mapBoth("group", TaggingPresetMenu.class);
-        parser.map("text", TaggingPresetItems.Text.class);
-        parser.map("link", TaggingPresetItems.Link.class);
-        parser.map("preset_link", TaggingPresetItems.PresetLink.class);
-        parser.mapOnStart("optional", TaggingPresetItems.Optional.class);
-        parser.mapOnStart("roles", TaggingPresetItems.Roles.class);
-        parser.map("role", TaggingPresetItems.Role.class);
-        parser.map("checkgroup", TaggingPresetItems.CheckGroup.class);
-        parser.map("check", TaggingPresetItems.Check.class);
-        parser.map("combo", TaggingPresetItems.Combo.class);
-        parser.map("multiselect", TaggingPresetItems.MultiSelect.class);
-        parser.map("label", TaggingPresetItems.Label.class);
-        parser.map("space", TaggingPresetItems.Space.class);
-        parser.map("key", TaggingPresetItems.Key.class);
-        parser.map("list_entry", TaggingPresetItems.PresetListEntry.class);
-        parser.map("item_separator", TaggingPresetItems.ItemSeparator.class);
-        parser.mapBoth("chunk", Chunk.class);
-        parser.map("reference", Reference.class);
-        return parser;
-    }
-
-    static class HashSetWithLast<E> extends LinkedHashSet<E> {
-        protected E last;
-
-        @Override
-        public boolean add(E e) {
-            last = e;
-            return super.add(e);
-        }
-
-        /**
-         * Returns the last inserted element.
-         */
-        public E getLast() {
-            return last;
-        }
-    }
-
-    /**
-     * Reads all tagging presets from the input reader.
-     * @param in The input reader
-     * @param validate if {@code true}, XML validation will be performed
-     * @return collection of tagging presets
-     * @throws SAXException if any XML error occurs
-     */
-    public static Collection<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
-        return readAll(in, validate, new HashSetWithLast<TaggingPreset>());
-    }
-
-    /**
-     * Reads all tagging presets from the input reader.
-     * @param in The input reader
-     * @param validate if {@code true}, XML validation will be performed
-     * @param all the accumulator for parsed tagging presets
-     * @return the accumulator
-     * @throws SAXException if any XML error occurs
-     */
-    static Collection<TaggingPreset> readAll(Reader in, boolean validate, HashSetWithLast<TaggingPreset> all) throws SAXException {
-        XmlObjectParser parser = buildParser();
-
-        /** to detect end of {@code <group>} */
-        TaggingPresetMenu lastmenu = null;
-        /** to detect end of reused {@code <group>} */
-        TaggingPresetMenu lastmenuOriginal = null;
-        TaggingPresetItems.Roles lastrole = null;
-        final List<TaggingPresetItems.Check> checks = new LinkedList<>();
-        List<TaggingPresetItems.PresetListEntry> listEntries = new LinkedList<>();
-        final Map<String, List<Object>> byId = new HashMap<>();
-        final Deque<String> lastIds = new ArrayDeque<>();
-        /** lastIdIterators contains non empty iterators of items to be handled before obtaining the next item from the XML parser */
-        final Deque<Iterator<Object>> lastIdIterators = new ArrayDeque<>();
-
-        if (validate) {
-            parser.startWithValidation(in, Main.getXMLBase()+"/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
-        } else {
-            parser.start(in);
-        }
-        while (parser.hasNext() || !lastIdIterators.isEmpty()) {
-            final Object o;
-            if (!lastIdIterators.isEmpty()) {
-                // obtain elements from lastIdIterators with higher priority
-                o = lastIdIterators.peek().next();
-                if (!lastIdIterators.peek().hasNext()) {
-                    // remove iterator if is empty
-                    lastIdIterators.pop();
-                }
-            } else {
-                o = parser.next();
-            }
-            if (o instanceof Chunk) {
-                if (!lastIds.isEmpty() && ((Chunk) o).id.equals(lastIds.peek())) {
-                    // pop last id on end of object, don't process further
-                    lastIds.pop();
-                    ((Chunk) o).id = null;
-                    continue;
-                } else {
-                    // if preset item contains an id, store a mapping for later usage
-                    String lastId = ((Chunk) o).id;
-                    lastIds.push(lastId);
-                    byId.put(lastId, new ArrayList<>());
-                    continue;
-                }
-            } else if (!lastIds.isEmpty()) {
-                // add object to mapping for later usage
-                byId.get(lastIds.peek()).add(o);
-                continue;
-            }
-            if (o instanceof Reference) {
-                // if o is a reference, obtain the corresponding objects from the mapping,
-                // and iterate over those before consuming the next element from parser.
-                final String ref = ((Reference) o).ref;
-                if (byId.get(ref) == null) {
-                    throw new SAXException(tr("Reference {0} is being used before it was defined", ref));
-                }
-                Iterator<Object> it = byId.get(ref).iterator();
-                if (it.hasNext()) {
-                    lastIdIterators.push(it);
-                } else {
-                    Main.warn("Ignoring reference '"+ref+"' denoting an empty chunk");
-                }
-                continue;
-            }
-            if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) {
-                all.getLast().data.addAll(checks);
-                checks.clear();
-            }
-            if (o instanceof TaggingPresetMenu) {
-                TaggingPresetMenu tp = (TaggingPresetMenu) o;
-                if (tp == lastmenu || tp == lastmenuOriginal) {
-                    lastmenu = tp.group;
-                } else {
-                    tp.group = lastmenu;
-                    if (all.contains(tp)) {
-                        lastmenuOriginal = tp;
-                        tp = (TaggingPresetMenu) Utils.filter(all, Predicates.<TaggingPreset>equalTo(tp)).iterator().next();
-                        lastmenuOriginal.group = null;
-                    } else {
-                        tp.setDisplayName();
-                        all.add(tp);
-                        lastmenuOriginal = null;
-                    }
-                    lastmenu = tp;
-                }
-                lastrole = null;
-            } else if (o instanceof TaggingPresetSeparator) {
-                TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
-                tp.group = lastmenu;
-                all.add(tp);
-                lastrole = null;
-            } else if (o instanceof TaggingPreset) {
-                TaggingPreset tp = (TaggingPreset) o;
-                tp.group = lastmenu;
-                tp.setDisplayName();
-                all.add(tp);
-                lastrole = null;
-            } else {
-                if (!all.isEmpty()) {
-                    if (o instanceof TaggingPresetItems.Roles) {
-                        all.getLast().data.add((TaggingPresetItem) o);
-                        if (all.getLast().roles != null) {
-                            throw new SAXException(tr("Roles cannot appear more than once"));
-                        }
-                        all.getLast().roles = (TaggingPresetItems.Roles) o;
-                        lastrole = (TaggingPresetItems.Roles) o;
-                    } else if (o instanceof TaggingPresetItems.Role) {
-                        if (lastrole == null)
-                            throw new SAXException(tr("Preset role element without parent"));
-                        lastrole.roles.add((TaggingPresetItems.Role) o);
-                    } else if (o instanceof TaggingPresetItems.Check) {
-                        checks.add((TaggingPresetItems.Check) o);
-                    } else if (o instanceof TaggingPresetItems.PresetListEntry) {
-                        listEntries.add((TaggingPresetItems.PresetListEntry) o);
-                    } else if (o instanceof TaggingPresetItems.CheckGroup) {
-                        all.getLast().data.add((TaggingPresetItem) o);
-                        // Make sure list of checks is empty to avoid adding checks several times
-                        // when used in chunks (fix #10801)
-                        ((TaggingPresetItems.CheckGroup) o).checks.clear();
-                        ((TaggingPresetItems.CheckGroup) o).checks.addAll(checks);
-                        checks.clear();
-                    } else {
-                        if (!checks.isEmpty()) {
-                            all.getLast().data.addAll(checks);
-                            checks.clear();
-                        }
-                        all.getLast().data.add((TaggingPresetItem) o);
-                        if (o instanceof TaggingPresetItems.ComboMultiSelect) {
-                            ((TaggingPresetItems.ComboMultiSelect) o).addListEntries(listEntries);
-                        } else if (o instanceof TaggingPresetItems.Key) {
-                            if (((TaggingPresetItems.Key) o).value == null) {
-                                ((TaggingPresetItems.Key) o).value = ""; // Fix #8530
-                            }
-                        }
-                        listEntries = new LinkedList<>();
-                        lastrole = null;
-                    }
-                } else
-                    throw new SAXException(tr("Preset sub element without parent"));
-            }
-        }
-        if (!all.isEmpty() && !checks.isEmpty()) {
-            all.getLast().data.addAll(checks);
-            checks.clear();
-        }
-        return all;
-    }
-
-    /**
-     * Reads all tagging presets from the given source.
-     * @param source a given filename, URL or internal resource
-     * @param validate if {@code true}, XML validation will be performed
-     * @return collection of tagging presets
-     * @throws SAXException if any XML error occurs
-     * @throws IOException if any I/O error occurs
-     */
-    public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
-        return readAll(source, validate, new HashSetWithLast<TaggingPreset>());
-    }
-
-    /**
-     * Reads all tagging presets from the given source.
-     * @param source a given filename, URL or internal resource
-     * @param validate if {@code true}, XML validation will be performed
-     * @param all the accumulator for parsed tagging presets
-     * @return the accumulator
-     * @throws SAXException if any XML error occurs
-     * @throws IOException if any I/O error occurs
-     */
-    static Collection<TaggingPreset> readAll(String source, boolean validate, HashSetWithLast<TaggingPreset> all)
-            throws SAXException, IOException {
-        Collection<TaggingPreset> tp;
-        CachedFile cf = new CachedFile(source).setHttpAccept(PRESET_MIME_TYPES);
-        try (
-            // zip may be null, but Java 7 allows it: https://blogs.oracle.com/darcy/entry/project_coin_null_try_with
-            InputStream zip = cf.findZipEntryInputStream("xml", "preset")
-        ) {
-            if (zip != null) {
-                zipIcons = cf.getFile();
-            }
-            try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) {
-                tp = readAll(new BufferedReader(r), validate, all);
-            }
-        }
-        return tp;
-    }
-
-    /**
-     * Reads all tagging presets from the given sources.
-     * @param sources Collection of tagging presets sources.
-     * @param validate if {@code true}, presets will be validated against XML schema
-     * @return Collection of all presets successfully read
-     */
-    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
-        return readAll(sources, validate, true);
-    }
-
-    /**
-     * Reads all tagging presets from the given sources.
-     * @param sources Collection of tagging presets sources.
-     * @param validate if {@code true}, presets will be validated against XML schema
-     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
-     * @return Collection of all presets successfully read
-     */
-    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate, boolean displayErrMsg) {
-        HashSetWithLast<TaggingPreset> allPresets = new HashSetWithLast<>();
-        for (String source : sources)  {
-            try {
-                readAll(source, validate, allPresets);
-            } catch (IOException e) {
-                Main.error(e, false);
-                Main.error(source);
-                if (source.startsWith("http")) {
-                    Main.addNetworkError(source, e);
-                }
-                if (displayErrMsg) {
-                    JOptionPane.showMessageDialog(
-                            Main.parent,
-                            tr("Could not read tagging preset source: {0}", source),
-                            tr("Error"),
-                            JOptionPane.ERROR_MESSAGE
-                            );
-                }
-            } catch (SAXException e) {
-                Main.error(e);
-                Main.error(source);
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        "<html>" + tr("Error parsing {0}: ", source) + "<br><br><table width=600>" + e.getMessage() + "</table></html>",
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                        );
-            }
-        }
-        return allPresets;
-    }
-
-    /**
-     * Reads all tagging presets from sources stored in preferences.
-     * @param validate if {@code true}, presets will be validated against XML schema
-     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
-     * @return Collection of all presets successfully read
-     */
-    public static Collection<TaggingPreset> readFromPreferences(boolean validate, boolean displayErrMsg) {
-        return readAll(getPresetSources(), validate, displayErrMsg);
-    }
-
-    public static File getZipIcons() {
-        return zipIcons;
-    }
-
-    /**
-     * Returns true if icon images should be loaded.
-     */
-    public static boolean isLoadIcons() {
-        return loadIcons;
-    }
-
-    /**
-     * Sets whether icon images should be loaded.
-     */
-    public static void setLoadIcons(boolean loadIcons) {
-        TaggingPresetReader.loadIcons = loadIcons;
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchAction.java	(revision 8862)
+++ 	(revision )
@@ -1,37 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.tools.Shortcut;
-
-/**
- * The tagging presets search action (F3).
- * @since 3388
- */
-public class TaggingPresetSearchAction extends JosmAction {
-
-    /**
-     * Constructs a new {@code TaggingPresetSearchAction}.
-     */
-    public TaggingPresetSearchAction() {
-        super(tr("Search preset"), "dialogs/search", tr("Show preset search dialog"),
-                Shortcut.registerShortcut("preset:search", tr("Search presets"), KeyEvent.VK_F3, Shortcut.DIRECT), false);
-        putValue("toolbar", "presets/search");
-        Main.toolbar.register(this);
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-
-        if (!Main.main.hasEditLayer())
-            return;
-
-        TaggingPresetSearchDialog.getInstance().showDialog();
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java	(revision 8862)
+++ 	(revision )
@@ -1,66 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-
-/**
- * The tagging presets search dialog (F3).
- * @since 3388
- */
-public final class TaggingPresetSearchDialog extends ExtendedDialog {
-
-    private TaggingPresetSelector selector;
-
-    private static TaggingPresetSearchDialog instance;
-
-    /**
-     * Returns the unique instance of {@code TaggingPresetSearchDialog}.
-     * @return the unique instance of {@code TaggingPresetSearchDialog}.
-     */
-    public static synchronized TaggingPresetSearchDialog getInstance() {
-        if (instance == null) {
-            instance = new TaggingPresetSearchDialog();
-        }
-        return instance;
-    }
-
-    private TaggingPresetSearchDialog() {
-        super(Main.parent, tr("Presets"), new String[] {tr("Select"), tr("Cancel")});
-        selector = new TaggingPresetSelector(true, true);
-        setContent(selector);
-        DataSet.addSelectionListener(selector);
-        selector.setDblClickListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                buttonAction(0, null);
-            }
-        });
-    }
-
-    @Override
-    public ExtendedDialog showDialog() {
-        selector.init();
-        super.showDialog();
-        selector.clearSelection();
-        return this;
-    }
-
-    @Override
-    protected void buttonAction(int buttonIndex, ActionEvent evt) {
-        super.buttonAction(buttonIndex, evt);
-        if (buttonIndex == 0) {
-            TaggingPreset preset = selector.getSelectedPreset();
-            if (preset != null) {
-                preset.actionPerformed(null);
-            }
-        }
-        selector.savePreferences();
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchPrimitiveDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchPrimitiveDialog.java	(revision 8862)
+++ 	(revision )
@@ -1,96 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.tools.Shortcut;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * A dialog that allows to select a preset and then selects all matching OSM objects.
- * @see org.openstreetmap.josm.gui.tagging.TaggingPresetSearchDialog
- */
-public final class TaggingPresetSearchPrimitiveDialog extends ExtendedDialog {
-
-    private TaggingPresetSelector selector;
-
-    private static TaggingPresetSearchPrimitiveDialog instance;
-
-    /**
-     * Returns the unique instance of {@code TaggingPresetSearchPrimitiveDialog}.
-     * @return the unique instance of {@code TaggingPresetSearchPrimitiveDialog}.
-     */
-    public static synchronized TaggingPresetSearchPrimitiveDialog getInstance() {
-        if (instance == null) {
-            instance = new TaggingPresetSearchPrimitiveDialog();
-        }
-        return instance;
-    }
-
-    TaggingPresetSearchPrimitiveDialog() {
-        super(Main.parent, tr("Presets"), new String[] {tr("Search"), tr("Cancel")});
-        selector = new TaggingPresetSelector(false, false);
-        setContent(selector);
-        selector.setDblClickListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                buttonAction(0, null);
-            }
-        });
-    }
-
-    @Override
-    public ExtendedDialog showDialog() {
-        selector.init();
-        super.showDialog();
-        selector.clearSelection();
-        return this;
-    }
-
-    @Override
-    protected void buttonAction(int buttonIndex, ActionEvent evt) {
-        super.buttonAction(buttonIndex, evt);
-        if (buttonIndex == 0) {
-            TaggingPreset preset = selector.getSelectedPreset();
-            if (preset != null) {
-                final Set<OsmPrimitive> matching = new HashSet<>(Utils.filter(Main.main.getCurrentDataSet().allPrimitives(), preset));
-                Main.main.getCurrentDataSet().setSelected(matching);
-            }
-        }
-    }
-
-    /**
-     * An action executing {@link TaggingPresetSearchPrimitiveDialog}.
-     */
-    public static class Action extends JosmAction {
-
-        /**
-         * Constructs a new {@link TaggingPresetSearchPrimitiveDialog.Action}.
-         */
-        public Action() {
-            super(tr("Search for objects by preset"), "dialogs/search", tr("Show preset search dialog"),
-                    Shortcut.registerShortcut("preset:search-objects", tr("Search for objects by preset"), KeyEvent.VK_F3, Shortcut.SHIFT),
-                    false);
-            putValue("toolbar", "presets/search-objects");
-            Main.toolbar.register(this);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (Main.main.hasEditLayer()) {
-                TaggingPresetSearchPrimitiveDialog.getInstance().showDialog();
-            }
-        }
-    }
-
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java	(revision 8862)
+++ 	(revision )
@@ -1,576 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Set;
-
-import javax.swing.AbstractAction;
-import javax.swing.AbstractListModel;
-import javax.swing.Action;
-import javax.swing.BoxLayout;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.Icon;
-import javax.swing.JCheckBox;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JScrollPane;
-import javax.swing.ListCellRenderer;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.SelectionChangedListener;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.KeyedItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
-import org.openstreetmap.josm.gui.widgets.JosmTextField;
-import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
-import org.openstreetmap.josm.tools.Predicate;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * GUI component to select tagging preset: the list with filter and two checkboxes
- * @since 6068
- */
-public class TaggingPresetSelector extends JPanel implements SelectionChangedListener {
-
-    private static final int CLASSIFICATION_IN_FAVORITES = 300;
-    private static final int CLASSIFICATION_NAME_MATCH = 300;
-    private static final int CLASSIFICATION_GROUP_MATCH = 200;
-    private static final int CLASSIFICATION_TAGS_MATCH = 100;
-
-    private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
-    private static final BooleanProperty ONLY_APPLICABLE  = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
-
-    private final JosmTextField edSearchText;
-    private final JList<TaggingPreset> lsResult;
-    private final JCheckBox ckOnlyApplicable;
-    private final JCheckBox ckSearchInTags;
-    private final Set<TaggingPresetType> typesInSelection = EnumSet.noneOf(TaggingPresetType.class);
-    private boolean typesInSelectionDirty = true;
-    private final transient PresetClassifications classifications = new PresetClassifications();
-    private final ResultListModel lsResultModel = new ResultListModel();
-
-    private final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
-
-    private transient ActionListener dblClickListener;
-    private transient ActionListener clickListener;
-
-    private static class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
-        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
-        @Override
-        public Component getListCellRendererComponent(JList<? extends TaggingPreset> list, TaggingPreset tp, int index,
-                boolean isSelected, boolean cellHasFocus) {
-            JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus);
-            result.setText(tp.getName());
-            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
-            return result;
-        }
-    }
-
-    private static class ResultListModel extends AbstractListModel<TaggingPreset> {
-
-        private transient List<PresetClassification> presets = new ArrayList<>();
-
-        public synchronized void setPresets(List<PresetClassification> presets) {
-            this.presets = presets;
-            fireContentsChanged(this, 0, Integer.MAX_VALUE);
-        }
-
-        @Override
-        public synchronized TaggingPreset getElementAt(int index) {
-            return presets.get(index).preset;
-        }
-
-        @Override
-        public synchronized int getSize() {
-            return presets.size();
-        }
-
-        public synchronized boolean isEmpty() {
-            return presets.isEmpty();
-        }
-    }
-
-    /**
-     * Computes the match ration of a {@link TaggingPreset} wrt. a searchString.
-     */
-    static class PresetClassification implements Comparable<PresetClassification> {
-        public final TaggingPreset preset;
-        public int classification;
-        public int favoriteIndex;
-        private final Collection<String> groups = new HashSet<>();
-        private final Collection<String> names = new HashSet<>();
-        private final Collection<String> tags = new HashSet<>();
-
-        PresetClassification(TaggingPreset preset) {
-            this.preset = preset;
-            TaggingPreset group = preset.group;
-            while (group != null) {
-                Collections.addAll(groups, group.getLocaleName().toLowerCase(Locale.ENGLISH).split("\\s"));
-                group = group.group;
-            }
-            Collections.addAll(names, preset.getLocaleName().toLowerCase(Locale.ENGLISH).split("\\s"));
-            for (TaggingPresetItem item: preset.data) {
-                if (item instanceof KeyedItem) {
-                    tags.add(((KeyedItem) item).key);
-                    if (item instanceof TaggingPresetItems.ComboMultiSelect) {
-                        final TaggingPresetItems.ComboMultiSelect cms = (TaggingPresetItems.ComboMultiSelect) item;
-                        if (Boolean.parseBoolean(cms.values_searchable)) {
-                            tags.addAll(cms.getDisplayValues());
-                        }
-                    }
-                    if (item instanceof Key && ((Key) item).value != null) {
-                        tags.add(((Key) item).value);
-                    }
-                } else if (item instanceof Roles) {
-                    for (Role role : ((Roles) item).roles) {
-                        tags.add(role.key);
-                    }
-                }
-            }
-        }
-
-        private int isMatching(Collection<String> values, String[] searchString) {
-            int sum = 0;
-            for (String word: searchString) {
-                boolean found = false;
-                boolean foundFirst = false;
-                for (String value: values) {
-                    int index = value.toLowerCase(Locale.ENGLISH).indexOf(word);
-                    if (index == 0) {
-                        foundFirst = true;
-                        break;
-                    } else if (index > 0) {
-                        found = true;
-                    }
-                }
-                if (foundFirst) {
-                    sum += 2;
-                } else if (found) {
-                    sum += 1;
-                } else
-                    return 0;
-            }
-            return sum;
-        }
-
-        int isMatchingGroup(String[] words) {
-            return isMatching(groups, words);
-        }
-
-        int isMatchingName(String[] words) {
-            return isMatching(names, words);
-        }
-
-        int isMatchingTags(String[] words) {
-            return isMatching(tags, words);
-        }
-
-        @Override
-        public int compareTo(PresetClassification o) {
-            int result = o.classification - classification;
-            if (result == 0)
-                return preset.getName().compareTo(o.preset.getName());
-            else
-                return result;
-        }
-
-        @Override
-        public String toString() {
-            return classification + " " + preset;
-        }
-    }
-
-    /**
-     * Constructs a new {@code TaggingPresetSelector}.
-     */
-    public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
-        super(new BorderLayout());
-        classifications.loadPresets(TaggingPresets.getTaggingPresets());
-
-        edSearchText = new JosmTextField();
-        edSearchText.getDocument().addDocumentListener(new DocumentListener() {
-            @Override
-            public void removeUpdate(DocumentEvent e) {
-                filterPresets();
-            }
-
-            @Override
-            public void insertUpdate(DocumentEvent e) {
-                filterPresets();
-            }
-
-            @Override
-            public void changedUpdate(DocumentEvent e) {
-                filterPresets();
-            }
-        });
-        edSearchText.addKeyListener(new KeyAdapter() {
-            @Override
-            public void keyPressed(KeyEvent e) {
-                switch (e.getKeyCode()) {
-                case KeyEvent.VK_DOWN:
-                    selectPreset(lsResult.getSelectedIndex() + 1);
-                    break;
-                case KeyEvent.VK_UP:
-                    selectPreset(lsResult.getSelectedIndex() - 1);
-                    break;
-                case KeyEvent.VK_PAGE_DOWN:
-                    selectPreset(lsResult.getSelectedIndex() + 10);
-                    break;
-                case KeyEvent.VK_PAGE_UP:
-                    selectPreset(lsResult.getSelectedIndex() - 10);
-                    break;
-                case KeyEvent.VK_HOME:
-                    selectPreset(0);
-                    break;
-                case KeyEvent.VK_END:
-                    selectPreset(lsResultModel.getSize());
-                    break;
-                }
-            }
-        });
-        add(edSearchText, BorderLayout.NORTH);
-
-        lsResult = new JList<>(lsResultModel);
-        lsResult.setCellRenderer(new ResultListCellRenderer());
-        lsResult.addMouseListener(new MouseAdapter() {
-            @Override
-            public void mouseClicked(MouseEvent e) {
-                if (e.getClickCount() > 1) {
-                    if (dblClickListener != null)
-                        dblClickListener.actionPerformed(null);
-                } else {
-                    if (clickListener != null)
-                        clickListener.actionPerformed(null);
-                }
-            }
-        });
-        add(new JScrollPane(lsResult), BorderLayout.CENTER);
-
-        JPanel pnChecks = new JPanel();
-        pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
-
-        if (displayOnlyApplicable) {
-            ckOnlyApplicable = new JCheckBox();
-            ckOnlyApplicable.setText(tr("Show only applicable to selection"));
-            pnChecks.add(ckOnlyApplicable);
-            ckOnlyApplicable.addItemListener(new ItemListener() {
-                @Override
-                public void itemStateChanged(ItemEvent e) {
-                    filterPresets();
-                }
-            });
-        } else {
-            ckOnlyApplicable = null;
-        }
-
-        if (displaySearchInTags) {
-            ckSearchInTags = new JCheckBox();
-            ckSearchInTags.setText(tr("Search in tags"));
-            ckSearchInTags.setSelected(SEARCH_IN_TAGS.get());
-            ckSearchInTags.addItemListener(new ItemListener() {
-                @Override
-                public void itemStateChanged(ItemEvent e) {
-                    filterPresets();
-                }
-            });
-            pnChecks.add(ckSearchInTags);
-        } else {
-            ckSearchInTags = null;
-        }
-
-        add(pnChecks, BorderLayout.SOUTH);
-
-        setPreferredSize(new Dimension(400, 300));
-        filterPresets();
-        JPopupMenu popupMenu = new JPopupMenu();
-        popupMenu.add(new AbstractAction(tr("Add toolbar button")) {
-            @Override
-            public void actionPerformed(ActionEvent ae) {
-                String res = getSelectedPreset().getToolbarString();
-                Main.toolbar.addCustomButton(res, -1, false);
-            }
-        });
-        lsResult.addMouseListener(new PopupMenuLauncher(popupMenu));
-    }
-
-    private synchronized void selectPreset(int newIndex) {
-        if (newIndex < 0) {
-            newIndex = 0;
-        }
-        if (newIndex > lsResultModel.getSize() - 1) {
-            newIndex = lsResultModel.getSize() - 1;
-        }
-        lsResult.setSelectedIndex(newIndex);
-        lsResult.ensureIndexIsVisible(newIndex);
-    }
-
-    /**
-     * Search expression can be in form: "group1/group2/name" where names can contain multiple words
-     */
-    private synchronized void filterPresets() {
-        //TODO Save favorites to file
-        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
-        boolean onlyApplicable = ckOnlyApplicable != null && ckOnlyApplicable.isSelected();
-        boolean inTags = ckSearchInTags != null && ckSearchInTags.isSelected();
-
-        DataSet ds = Main.main.getCurrentDataSet();
-        Collection<OsmPrimitive> selected = (ds == null) ? Collections.<OsmPrimitive>emptyList() : ds.getSelected();
-        final List<PresetClassification> result = classifications.getMatchingPresets(
-                text, onlyApplicable, inTags, getTypesInSelection(), selected);
-
-        TaggingPreset oldPreset = getSelectedPreset();
-        lsResultModel.setPresets(result);
-        TaggingPreset newPreset = getSelectedPreset();
-        if (!Objects.equals(oldPreset, newPreset)) {
-            int[] indices = lsResult.getSelectedIndices();
-            for (ListSelectionListener listener : listSelectionListeners) {
-                listener.valueChanged(new ListSelectionEvent(lsResult, lsResult.getSelectedIndex(),
-                        indices.length > 0 ? indices[indices.length-1] : -1, false));
-            }
-        }
-    }
-
-    /**
-     * A collection of {@link PresetClassification}s with the functionality of filtering wrt. searchString.
-     */
-    static class PresetClassifications implements Iterable<PresetClassification> {
-
-        private final List<PresetClassification> classifications = new ArrayList<>();
-
-        public List<PresetClassification> getMatchingPresets(String searchText, boolean onlyApplicable, boolean inTags,
-                Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
-            final String[] groupWords;
-            final String[] nameWords;
-
-            if (searchText.contains("/")) {
-                groupWords = searchText.substring(0, searchText.lastIndexOf('/')).split("[\\s/]");
-                nameWords = searchText.substring(searchText.indexOf('/') + 1).split("\\s");
-            } else {
-                groupWords = null;
-                nameWords = searchText.split("\\s");
-            }
-
-            return getMatchingPresets(groupWords, nameWords, onlyApplicable, inTags, presetTypes, selectedPrimitives);
-        }
-
-        public List<PresetClassification> getMatchingPresets(String[] groupWords, String[] nameWords, boolean onlyApplicable,
-                boolean inTags, Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
-
-            final List<PresetClassification> result = new ArrayList<>();
-            for (PresetClassification presetClassification : classifications) {
-                TaggingPreset preset = presetClassification.preset;
-                presetClassification.classification = 0;
-
-                if (onlyApplicable) {
-                    boolean suitable = preset.typeMatches(presetTypes);
-
-                    if (!suitable && preset.types.contains(TaggingPresetType.RELATION)
-                            && preset.roles != null && !preset.roles.roles.isEmpty()) {
-                        final Predicate<Role> memberExpressionMatchesOnePrimitive = new Predicate<Role>() {
-                            @Override
-                            public boolean evaluate(Role object) {
-                                return object.memberExpression != null
-                                        && Utils.exists(selectedPrimitives, object.memberExpression);
-                            }
-                        };
-                        suitable = Utils.exists(preset.roles.roles, memberExpressionMatchesOnePrimitive);
-                        // keep the preset to allow the creation of new relations
-                    }
-                    if (!suitable) {
-                        continue;
-                    }
-                }
-
-                if (groupWords != null && presetClassification.isMatchingGroup(groupWords) == 0) {
-                    continue;
-                }
-
-                int matchName = presetClassification.isMatchingName(nameWords);
-
-                if (matchName == 0) {
-                    if (groupWords == null) {
-                        int groupMatch = presetClassification.isMatchingGroup(nameWords);
-                        if (groupMatch > 0) {
-                            presetClassification.classification = CLASSIFICATION_GROUP_MATCH + groupMatch;
-                        }
-                    }
-                    if (presetClassification.classification == 0 && inTags) {
-                        int tagsMatch = presetClassification.isMatchingTags(nameWords);
-                        if (tagsMatch > 0) {
-                            presetClassification.classification = CLASSIFICATION_TAGS_MATCH + tagsMatch;
-                        }
-                    }
-                } else {
-                    presetClassification.classification = CLASSIFICATION_NAME_MATCH + matchName;
-                }
-
-                if (presetClassification.classification > 0) {
-                    presetClassification.classification += presetClassification.favoriteIndex;
-                    result.add(presetClassification);
-                }
-            }
-
-            Collections.sort(result);
-            return result;
-
-        }
-
-        public void clear() {
-            classifications.clear();
-        }
-
-        public void loadPresets(Collection<TaggingPreset> presets) {
-            for (TaggingPreset preset : presets) {
-                if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
-                    continue;
-                }
-                classifications.add(new PresetClassification(preset));
-            }
-        }
-
-        @Override
-        public Iterator<PresetClassification> iterator() {
-            return classifications.iterator();
-        }
-    }
-
-    private Set<TaggingPresetType> getTypesInSelection() {
-        if (typesInSelectionDirty) {
-            synchronized (typesInSelection) {
-                typesInSelectionDirty = false;
-                typesInSelection.clear();
-                if (Main.main == null || Main.main.getCurrentDataSet() == null) return typesInSelection;
-                for (OsmPrimitive primitive : Main.main.getCurrentDataSet().getSelected()) {
-                    typesInSelection.add(TaggingPresetType.forPrimitive(primitive));
-                }
-            }
-        }
-        return typesInSelection;
-    }
-
-    @Override
-    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
-        typesInSelectionDirty = true;
-    }
-
-    public synchronized void init() {
-        if (ckOnlyApplicable != null) {
-            ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
-            ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
-        }
-        listSelectionListeners.clear();
-        edSearchText.setText("");
-        filterPresets();
-    }
-
-    public void init(Collection<TaggingPreset> presets) {
-        classifications.clear();
-        classifications.loadPresets(presets);
-        init();
-    }
-
-    public synchronized void clearSelection() {
-        lsResult.getSelectionModel().clearSelection();
-    }
-
-    /**
-     * Save checkbox values in preferences for future reuse
-     */
-    public void savePreferences() {
-        if (ckSearchInTags != null) {
-            SEARCH_IN_TAGS.put(ckSearchInTags.isSelected());
-        }
-        if (ckOnlyApplicable != null && ckOnlyApplicable.isEnabled()) {
-            ONLY_APPLICABLE.put(ckOnlyApplicable.isSelected());
-        }
-    }
-
-    /**
-     * Determines, which preset is selected at the current moment
-     * @return selected preset (as action)
-     */
-    public synchronized TaggingPreset getSelectedPreset() {
-        if (lsResultModel.isEmpty()) return null;
-        int idx = lsResult.getSelectedIndex();
-        if (idx < 0 || idx >= lsResultModel.getSize()) {
-            idx = 0;
-        }
-        TaggingPreset preset = lsResultModel.getElementAt(idx);
-        for (PresetClassification pc: classifications) {
-            if (pc.preset == preset) {
-                pc.favoriteIndex = CLASSIFICATION_IN_FAVORITES;
-            } else if (pc.favoriteIndex > 0) {
-                pc.favoriteIndex--;
-            }
-        }
-        return preset;
-    }
-
-    public synchronized void setSelectedPreset(TaggingPreset p) {
-        lsResult.setSelectedValue(p, true);
-    }
-
-    public synchronized int getItemCount() {
-        return lsResultModel.getSize();
-    }
-
-    public void setDblClickListener(ActionListener dblClickListener) {
-        this.dblClickListener = dblClickListener;
-    }
-
-    public void setClickListener(ActionListener clickListener) {
-        this.clickListener = clickListener;
-    }
-
-    /**
-     * Adds a selection listener to the presets list.
-     * @param selectListener The list selection listener
-     * @since 7412
-     */
-    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
-        lsResult.getSelectionModel().addListSelectionListener(selectListener);
-        listSelectionListeners.add(selectListener);
-    }
-
-    /**
-     * Removes a selection listener from the presets list.
-     * @param selectListener The list selection listener
-     * @since 7412
-     */
-    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
-        listSelectionListeners.remove(selectListener);
-        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSeparator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSeparator.java	(revision 8862)
+++ 	(revision )
@@ -1,11 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-/**
- * Class used to represent a {@link javax.swing.JSeparator} inside tagging preset menu.
- * @since 895
- */
-public class TaggingPresetSeparator extends TaggingPreset {
-    @Override
-    public void setDisplayName() {}
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetType.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetType.java	(revision 8862)
+++ 	(revision )
@@ -1,80 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-
-/**
- * Enumeration of OSM primitive types associated with names and icons
- * @since 6068
- */
-public enum TaggingPresetType {
-    /** Node */
-    NODE(/* ICON */ "Mf_node", "node"),
-    /** Way */
-    WAY(/* ICON */ "Mf_way", "way"),
-    /** Relation */
-    RELATION(/* ICON */ "Mf_relation", "relation"),
-    /** Closed way */
-    CLOSEDWAY(/* ICON */ "Mf_closedway", "closedway");
-    private final String iconName;
-    private final String name;
-
-    TaggingPresetType(String iconName, String name) {
-        this.iconName = iconName + ".svg";
-        this.name = name;
-    }
-
-    /**
-     * Replies the SVG icon name.
-     * @return the SVG icon name
-     */
-    public String getIconName() {
-        return iconName;
-    }
-
-    /**
-     * Replies the name, as used in XML presets.
-     * @return the name: "node", "way", "relation" or "closedway"
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Determines the {@code TaggingPresetType} of a given primitive.
-     * @param p The OSM primitive
-     * @return the {@code TaggingPresetType} of {@code p}
-     */
-    public static TaggingPresetType forPrimitive(OsmPrimitive p) {
-        return forPrimitiveType(p.getDisplayType());
-    }
-
-    /**
-     * Determines the {@code TaggingPresetType} of a given primitive type.
-     * @param type The OSM primitive type
-     * @return the {@code TaggingPresetType} of {@code type}
-     */
-    public static TaggingPresetType forPrimitiveType(OsmPrimitiveType type) {
-        if (type == OsmPrimitiveType.NODE) return NODE;
-        if (type == OsmPrimitiveType.WAY) return WAY;
-        if (type == OsmPrimitiveType.CLOSEDWAY) return CLOSEDWAY;
-        if (type == OsmPrimitiveType.RELATION || type == OsmPrimitiveType.MULTIPOLYGON)
-                return RELATION;
-        throw new IllegalArgumentException("Unexpected primitive type: " + type);
-    }
-
-    /**
-     * Determines the {@code TaggingPresetType} from a given string.
-     * @param type The OSM primitive type as string ("node", "way", "relation" or "closedway")
-     * @return the {@code TaggingPresetType} from {@code type}
-     */
-    public static TaggingPresetType fromString(String type) {
-        for (TaggingPresetType t : TaggingPresetType.values()) {
-            if (t.getName().equals(type)) {
-                return t;
-            }
-        }
-        return null;
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/tagging/TaggingPresets.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresets.java	(revision 8862)
+++ 	(revision )
@@ -1,122 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JSeparator;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
-
-/**
- * Class holding Tagging Presets and allowing to manage them.
- * @since 7100
- */
-public final class TaggingPresets {
-
-    /** The collection of tagging presets */
-    private static final Collection<TaggingPreset> taggingPresets = new ArrayList<>();
-
-    /** The collection of listeners */
-    private static final Collection<TaggingPresetListener> listeners = new ArrayList<>();
-
-    private TaggingPresets() {
-        // Hide constructor for utility classes
-    }
-
-    /**
-     * Initializes tagging presets from preferences.
-     */
-    public static void readFromPreferences() {
-        taggingPresets.clear();
-        taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false));
-    }
-
-    /**
-     * Initialize the tagging presets (load and may display error)
-     */
-    public static void initialize() {
-        readFromPreferences();
-        for (TaggingPreset tp: taggingPresets) {
-            if (!(tp instanceof TaggingPresetSeparator)) {
-                Main.toolbar.register(tp);
-            }
-        }
-        if (taggingPresets.isEmpty()) {
-            Main.main.menu.presetsMenu.setVisible(false);
-        } else {
-            AutoCompletionManager.cachePresets(taggingPresets);
-            Map<TaggingPresetMenu, JMenu> submenus = new HashMap<>();
-            for (final TaggingPreset p : taggingPresets) {
-                JMenu m = p.group != null ? submenus.get(p.group) : Main.main.menu.presetsMenu;
-                if (m == null && p.group != null) {
-                    Main.error("No tagging preset submenu for " + p.group);
-                } else if (m == null) {
-                    Main.error("No tagging preset menu. Tagging preset " + p + " won't be available there");
-                } else if (p instanceof TaggingPresetSeparator) {
-                    m.add(new JSeparator());
-                } else if (p instanceof TaggingPresetMenu) {
-                    JMenu submenu = new JMenu(p);
-                    submenu.setText(p.getLocaleName());
-                    ((TaggingPresetMenu) p).menu = submenu;
-                    submenus.put((TaggingPresetMenu) p, submenu);
-                    m.add(submenu);
-                } else {
-                    JMenuItem mi = new JMenuItem(p);
-                    mi.setText(p.getLocaleName());
-                    m.add(mi);
-                }
-            }
-        }
-        if (Main.pref.getBoolean("taggingpreset.sortmenu")) {
-            TaggingPresetMenu.sortMenu(Main.main.menu.presetsMenu);
-        }
-    }
-
-    /**
-     * Replies a new collection containing all tagging presets.
-     * @return a new collection containing all tagging presets. Empty if presets are not initialized (never null)
-     */
-    public static Collection<TaggingPreset> getTaggingPresets() {
-        return new ArrayList<>(taggingPresets);
-    }
-
-    /**
-     * Adds a list of tagging presets to the current list.
-     * @param presets The tagging presets to add
-     */
-    public static void addTaggingPresets(Collection<TaggingPreset> presets) {
-        if (presets != null) {
-            if (taggingPresets.addAll(presets)) {
-                for (TaggingPresetListener listener : listeners) {
-                    listener.taggingPresetsModified();
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds a tagging preset listener.
-     * @param listener The listener to add
-     */
-    public static void addListener(TaggingPresetListener listener) {
-        if (listener != null) {
-            listeners.add(listener);
-        }
-    }
-
-    /**
-     * Removes a tagging preset listener.
-     * @param listener The listener to remove
-     */
-    public static void removeListener(TaggingPresetListener listener) {
-        if (listener != null) {
-            listeners.remove(listener);
-        }
-    }
-}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 8863)
@@ -28,8 +28,10 @@
 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.MultiMap;
@@ -216,6 +218,6 @@
 
     protected static void cachePresetItem(TaggingPreset p, TaggingPresetItem item) {
-        if (item instanceof TaggingPresetItems.KeyedItem) {
-            TaggingPresetItems.KeyedItem ki = (TaggingPresetItems.KeyedItem) item;
+        if (item instanceof KeyedItem) {
+            KeyedItem ki = (KeyedItem) item;
             if (ki.key != null && ki.getValues() != null) {
                 try {
@@ -225,13 +227,13 @@
                 }
             }
-        } else if (item instanceof TaggingPresetItems.Roles) {
-            TaggingPresetItems.Roles r = (TaggingPresetItems.Roles) item;
-            for (TaggingPresetItems.Role i : r.roles) {
+        } else if (item instanceof Roles) {
+            Roles r = (Roles) item;
+            for (Role i : r.roles) {
                 if (i.key != null) {
                     PRESET_ROLE_CACHE.add(i.key);
                 }
             }
-        } else if (item instanceof TaggingPresetItems.CheckGroup) {
-            for (TaggingPresetItems.KeyedItem check : ((TaggingPresetItems.CheckGroup) item).checks) {
+        } else if (item instanceof CheckGroup) {
+            for (KeyedItem check : ((CheckGroup) item).checks) {
                 cachePresetItem(p, check);
             }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/package-info.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/package-info.java	(revision 8862)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/package-info.java	(revision 8863)
@@ -2,6 +2,6 @@
 
 /**
- * Provides classes for handling edition of OSM tags: tagging presets, tag tables, tag editors.
- * Autocompletion of tags is dealt with a subpackage.
+ * Provides classes for handling edition of OSM tags: tag tables, tag editors.
+ * Autocompletion of tags and tagging presets are dealt with a subpackage.
  */
 package org.openstreetmap.josm.gui.tagging;
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 8863)
@@ -0,0 +1,553 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.search.SearchCompiler;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.Label;
+import org.openstreetmap.josm.gui.tagging.presets.items.Link;
+import org.openstreetmap.josm.gui.tagging.presets.items.Optional;
+import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
+import org.openstreetmap.josm.gui.tagging.presets.items.Space;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ImageResource;
+import org.openstreetmap.josm.tools.Predicate;
+import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.tools.template_engine.ParseError;
+import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
+import org.openstreetmap.josm.tools.template_engine.TemplateParser;
+import org.xml.sax.SAXException;
+
+/**
+ * This class read encapsulate one tagging preset. A class method can
+ * read in all predefined presets, either shipped with JOSM or that are
+ * in the config directory.
+ *
+ * It is also able to construct dialogs out of preset definitions.
+ * @since 294
+ */
+public class TaggingPreset extends AbstractAction implements MapView.LayerChangeListener, Predicate<OsmPrimitive> {
+
+    public static final int DIALOG_ANSWER_APPLY = 1;
+    public static final int DIALOG_ANSWER_NEW_RELATION = 2;
+    public static final int DIALOG_ANSWER_CANCEL = 3;
+
+    public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
+
+    public TaggingPresetMenu group;
+    public String name;
+    public String iconName;
+    public String name_context;
+    public String locale_name;
+    public boolean preset_name_label;
+
+    /**
+     * The types as preparsed collection.
+     */
+    public Set<TaggingPresetType> types;
+    public transient List<TaggingPresetItem> data = new LinkedList<>();
+    public transient Roles roles;
+    public transient TemplateEntry nameTemplate;
+    public transient Match nameTemplateFilter;
+
+    /**
+     * True whenever the original selection given into createSelection was empty
+     */
+    private boolean originalSelectionEmpty;
+
+    /**
+     * Create an empty tagging preset. This will not have any items and
+     * will be an empty string as text. createPanel will return null.
+     * Use this as default item for "do not select anything".
+     */
+    public TaggingPreset() {
+        MapView.addLayerChangeListener(this);
+        updateEnabledState();
+    }
+
+    /**
+     * Change the display name without changing the toolbar value.
+     */
+    public void setDisplayName() {
+        putValue(Action.NAME, getName());
+        putValue("toolbar", "tagging_" + getRawName());
+        putValue(OPTIONAL_TOOLTIP_TEXT, group != null ?
+                tr("Use preset ''{0}'' of group ''{1}''", getLocaleName(), group.getName()) :
+                    tr("Use preset ''{0}''", getLocaleName()));
+    }
+
+    public String getLocaleName() {
+        if (locale_name == null) {
+            if (name_context != null) {
+                locale_name = trc(name_context, TaggingPresetItem.fixPresetString(name));
+            } else {
+                locale_name = tr(TaggingPresetItem.fixPresetString(name));
+            }
+        }
+        return locale_name;
+    }
+
+    /**
+     * Returns the translated name of this preset, prefixed with the group names it belongs to.
+     */
+    public String getName() {
+        return group != null ? group.getName() + '/' + getLocaleName() : getLocaleName();
+    }
+
+    /**
+     * Returns the non translated name of this preset, prefixed with the (non translated) group names it belongs to.
+     */
+    public String getRawName() {
+        return group != null ? group.getRawName() + '/' + name : name;
+    }
+
+    /**
+     * Returns the preset icon.
+     * @return The preset icon, or {@code null} if none defined
+     * @since 6403
+     */
+    public final ImageIcon getIcon() {
+        Object icon = getValue(Action.SMALL_ICON);
+        if (icon instanceof ImageIcon) {
+            return (ImageIcon) icon;
+        }
+        return null;
+    }
+
+    /**
+     * Called from the XML parser to set the icon.
+     * The loading task is performed in the background in order to speedup startup.
+     */
+    public void setIcon(final String iconName) {
+        this.iconName = iconName;
+        if (!TaggingPresetReader.isLoadIcons()) {
+            return;
+        }
+        File arch = TaggingPresetReader.getZipIcons();
+        final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
+        ImageProvider imgProv = new ImageProvider(iconName);
+        imgProv.setDirs(s);
+        imgProv.setId("presets");
+        imgProv.setArchive(arch);
+        imgProv.setOptional(true);
+        imgProv.getInBackground(new ImageProvider.ImageResourceCallback() {
+            @Override
+            public void finished(final ImageResource result) {
+                if (result != null) {
+                    GuiHelper.runInEDT(new Runnable() {
+                        @Override
+                        public void run() {
+                            result.getImageIcon(TaggingPreset.this);
+                        }
+                    });
+                } else {
+                    Main.warn("Could not get presets icon " + iconName);
+                }
+            }
+        });
+    }
+
+    /**
+     * Called from the XML parser to set the types this preset affects.
+     */
+    public void setType(String types) throws SAXException {
+        this.types = TaggingPresetItem.getType(types);
+    }
+
+    public void setName_template(String pattern) throws SAXException {
+        try {
+            this.nameTemplate = new TemplateParser(pattern).parse();
+        } catch (ParseError e) {
+            Main.error("Error while parsing " + pattern + ": " + e.getMessage());
+            throw new SAXException(e);
+        }
+    }
+
+    public void setName_template_filter(String filter) throws SAXException {
+        try {
+            this.nameTemplateFilter = SearchCompiler.compile(filter);
+        } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
+            Main.error("Error while parsing" + filter + ": " + e.getMessage());
+            throw new SAXException(e);
+        }
+    }
+
+    private static class PresetPanel extends JPanel {
+        private boolean hasElements;
+
+        PresetPanel() {
+            super(new GridBagLayout());
+        }
+    }
+
+    public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
+        if (data == null)
+            return null;
+        PresetPanel p = new PresetPanel();
+        List<Link> l = new LinkedList<>();
+        List<PresetLink> presetLink = new LinkedList<>();
+        if (types != null) {
+            JPanel pp = new JPanel();
+            for (TaggingPresetType t : types) {
+                JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
+                la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
+                pp.add(la);
+            }
+            p.add(pp, GBC.eol());
+        }
+        if (preset_name_label) {
+            Label.addLabel(p, getIcon(), getName());
+        }
+
+        boolean presetInitiallyMatches = !selected.isEmpty() && Utils.forAll(selected, this);
+        JPanel items = new JPanel(new GridBagLayout());
+        for (TaggingPresetItem i : data) {
+            if (i instanceof Link) {
+                l.add((Link) i);
+                p.hasElements = true;
+            } else if (i instanceof PresetLink) {
+                presetLink.add((PresetLink) i);
+            } else {
+                if (i.addToPanel(items, selected, presetInitiallyMatches)) {
+                    p.hasElements = true;
+                }
+            }
+        }
+        p.add(items, GBC.eol().fill());
+        if (selected.isEmpty() && !supportsRelation()) {
+            GuiHelper.setEnabledRec(items, false);
+        }
+
+        // add PresetLink
+        if (!presetLink.isEmpty()) {
+            p.add(new JLabel(tr("Edit also …")), GBC.eol().insets(0, 8, 0, 0));
+            for (PresetLink link : presetLink) {
+                link.addToPanel(p, selected, presetInitiallyMatches);
+            }
+        }
+
+        // add Link
+        for (Link link : l) {
+            link.addToPanel(p, selected, presetInitiallyMatches);
+        }
+
+        // "Add toolbar button"
+        JToggleButton tb = new JToggleButton(new ToolbarButtonAction());
+        tb.setFocusable(false);
+        p.add(tb, GBC.std(0, 0).anchor(GBC.LINE_END));
+        return p;
+    }
+
+    public boolean isShowable() {
+        for (TaggingPresetItem i : data) {
+            if (!(i instanceof Optional || i instanceof Space || i instanceof Key))
+                return true;
+        }
+        return false;
+    }
+
+    public String suggestRoleForOsmPrimitive(OsmPrimitive osm) {
+        if (roles != null && osm != null) {
+            for (Role i : roles.roles) {
+                if (i.memberExpression != null && i.memberExpression.match(osm)
+                        && (i.types == null || i.types.isEmpty() || i.types.contains(TaggingPresetType.forPrimitive(osm)))) {
+                    return i.key;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (Main.main == null) {
+            return;
+        }
+        DataSet ds = Main.main.getCurrentDataSet();
+        Collection<OsmPrimitive> participants = Collections.emptyList();
+        if (Main.main != null && ds != null) {
+            participants = ds.getSelected();
+        }
+
+        // Display dialog even if no data layer (used by preset-tagging-tester plugin)
+        Collection<OsmPrimitive> sel = createSelection(participants);
+        int answer = showDialog(sel, supportsRelation());
+
+        if (ds == null) {
+            return;
+        }
+
+        if (!sel.isEmpty() && answer == DIALOG_ANSWER_APPLY) {
+            Command cmd = createCommand(sel, getChangedTags());
+            if (cmd != null) {
+                Main.main.undoRedo.add(cmd);
+            }
+        } else if (answer == DIALOG_ANSWER_NEW_RELATION) {
+            final Relation r = new Relation();
+            final Collection<RelationMember> members = new HashSet<>();
+            for (Tag t : getChangedTags()) {
+                r.put(t.getKey(), t.getValue());
+            }
+            for (OsmPrimitive osm : ds.getSelected()) {
+                String role = suggestRoleForOsmPrimitive(osm);
+                RelationMember rm = new RelationMember(role == null ? "" : role, osm);
+                r.addMember(rm);
+                members.add(rm);
+            }
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    RelationEditor.getEditor(Main.main.getEditLayer(), r, members).setVisible(true);
+                }
+            });
+        }
+        ds.setSelected(ds.getSelected()); // force update
+    }
+
+    private static class PresetDialog extends ExtendedDialog {
+        PresetDialog(Component content, String title, ImageIcon icon, boolean disableApply, boolean showNewRelation) {
+            super(Main.parent, title,
+                    showNewRelation ?
+                            new String[] {tr("Apply Preset"), tr("New relation"), tr("Cancel")} :
+                                new String[] {tr("Apply Preset"), tr("Cancel")},
+                                true);
+            if (icon != null)
+                setIconImage(icon.getImage());
+            contentInsets = new Insets(10, 5, 0, 5);
+            if (showNewRelation) {
+                setButtonIcons(new String[] {"ok", "dialogs/addrelation", "cancel" });
+            } else {
+                setButtonIcons(new String[] {"ok", "cancel" });
+            }
+            setContent(content);
+            setDefaultButton(1);
+            setupDialog();
+            buttons.get(0).setEnabled(!disableApply);
+            buttons.get(0).setToolTipText(title);
+            // Prevent dialogs of being too narrow (fix #6261)
+            Dimension d = getSize();
+            if (d.width < 350) {
+                d.width = 350;
+                setSize(d);
+            }
+            showDialog();
+        }
+    }
+
+    public int showDialog(Collection<OsmPrimitive> sel, boolean showNewRelation) {
+        PresetPanel p = createPanel(sel);
+        if (p == null)
+            return DIALOG_ANSWER_CANCEL;
+
+        int answer = 1;
+        boolean canCreateRelation = types == null || types.contains(TaggingPresetType.RELATION);
+        if (originalSelectionEmpty && !canCreateRelation) {
+            new Notification(
+                    tr("The preset <i>{0}</i> cannot be applied since nothing has been selected!", getLocaleName()))
+                    .setIcon(JOptionPane.WARNING_MESSAGE)
+                    .show();
+            return DIALOG_ANSWER_CANCEL;
+        } else if (sel.isEmpty() && !canCreateRelation) {
+            new Notification(
+                    tr("The preset <i>{0}</i> cannot be applied since the selection is unsuitable!", getLocaleName()))
+                    .setIcon(JOptionPane.WARNING_MESSAGE)
+                    .show();
+            return DIALOG_ANSWER_CANCEL;
+        } else if (p.getComponentCount() != 0 && (sel.isEmpty() || p.hasElements)) {
+            String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
+            if (sel.isEmpty()) {
+                if (originalSelectionEmpty) {
+                    title = tr("Nothing selected!");
+                } else {
+                    title = tr("Selection unsuitable!");
+                }
+            }
+
+            answer = new PresetDialog(p, title, preset_name_label ? null : (ImageIcon) getValue(Action.SMALL_ICON),
+                    sel.isEmpty(), showNewRelation).getValue();
+        }
+        if (!showNewRelation && answer == 2)
+            return DIALOG_ANSWER_CANCEL;
+        else
+            return answer;
+    }
+
+    /**
+     * Removes all unsuitable OsmPrimitives from the given list
+     * @param participants List of possible OsmPrimitives to tag
+     * @return Cleaned list with suitable OsmPrimitives only
+     */
+    public Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) {
+        originalSelectionEmpty = participants.isEmpty();
+        Collection<OsmPrimitive> sel = new LinkedList<>();
+        for (OsmPrimitive osm : participants) {
+            if (typeMatches(EnumSet.of(TaggingPresetType.forPrimitive(osm)))) {
+                sel.add(osm);
+            }
+        }
+        return sel;
+    }
+
+    public List<Tag> getChangedTags() {
+        List<Tag> result = new ArrayList<>();
+        for (TaggingPresetItem i: data) {
+            i.addCommands(result);
+        }
+        return result;
+    }
+
+    public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) {
+        List<Command> cmds = new ArrayList<>();
+        for (Tag tag: changedTags) {
+            cmds.add(new ChangePropertyCommand(sel, tag.getKey(), tag.getValue()));
+        }
+
+        if (cmds.isEmpty())
+            return null;
+        else if (cmds.size() == 1)
+            return cmds.get(0);
+        else
+            return new SequenceCommand(tr("Change Tags"), cmds);
+    }
+
+    private boolean supportsRelation() {
+        return types == null || types.contains(TaggingPresetType.RELATION);
+    }
+
+    protected final void updateEnabledState() {
+        setEnabled(Main.main != null && Main.main.getCurrentDataSet() != null);
+    }
+
+    @Override
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void layerAdded(Layer newLayer) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void layerRemoved(Layer oldLayer) {
+        updateEnabledState();
+    }
+
+    @Override
+    public String toString() {
+        return (types == null ? "" : types) + " " + name;
+    }
+
+    public boolean typeMatches(Collection<TaggingPresetType> t) {
+        return t == null || types == null || types.containsAll(t);
+    }
+
+    @Override
+    public boolean evaluate(OsmPrimitive p) {
+        return matches(EnumSet.of(TaggingPresetType.forPrimitive(p)), p.getKeys(), false);
+    }
+
+    public boolean matches(Collection<TaggingPresetType> t, Map<String, String> tags, boolean onlyShowable) {
+        if (onlyShowable && !isShowable())
+            return false;
+        else if (!typeMatches(t))
+            return false;
+        boolean atLeastOnePositiveMatch = false;
+        for (TaggingPresetItem item : data) {
+            Boolean m = item.matches(tags);
+            if (m != null && !m)
+                return false;
+            else if (m != null) {
+                atLeastOnePositiveMatch = true;
+            }
+        }
+        return atLeastOnePositiveMatch;
+    }
+
+    public static Collection<TaggingPreset> getMatchingPresets(final Collection<TaggingPresetType> t,
+            final Map<String, String> tags, final boolean onlyShowable) {
+        return Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() {
+            @Override
+            public boolean evaluate(TaggingPreset object) {
+                return object.matches(t, tags, onlyShowable);
+            }
+        });
+    }
+
+    /**
+     * Action that adds or removes the button on main toolbar
+     */
+    public class ToolbarButtonAction extends AbstractAction {
+        private final int toolbarIndex;
+
+        /**
+         * Constructs a new {@code ToolbarButtonAction}.
+         */
+        public ToolbarButtonAction() {
+            super("", ImageProvider.get("dialogs", "pin"));
+            putValue(SHORT_DESCRIPTION, tr("Add or remove toolbar button"));
+            List<String> t = new LinkedList<>(ToolbarPreferences.getToolString());
+            toolbarIndex = t.indexOf(getToolbarString());
+            putValue(SELECTED_KEY, toolbarIndex >= 0);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent ae) {
+            String res = getToolbarString();
+            Main.toolbar.addCustomButton(res, toolbarIndex, true);
+        }
+    }
+
+    public String getToolbarString() {
+        ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null);
+        return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(this));
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetHandler.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetHandler.java	(revision 8863)
@@ -0,0 +1,14 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+
+public interface TaggingPresetHandler {
+    Collection<OsmPrimitive> getSelection();
+
+    void updateTags(List<Tag> tags);
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 8863)
@@ -0,0 +1,134 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.xml.sax.SAXException;
+
+/**
+ * Class that represents single part of a preset - one field or text label that is shown to user
+ * @since 6068
+ */
+public abstract class TaggingPresetItem {
+
+    // cache the parsing of types using a LRU cache (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
+    private static final Map<String, Set<TaggingPresetType>> TYPE_CACHE = new LinkedHashMap<>(16, 1.1f, true);
+
+    protected void initAutoCompletionField(AutoCompletingTextField field, String... key) {
+        initAutoCompletionField(field, Arrays.asList(key));
+    }
+
+    protected void initAutoCompletionField(AutoCompletingTextField field, List<String> keys) {
+        if (Main.main == null) return;
+        OsmDataLayer layer = Main.main.getEditLayer();
+        if (layer == null) {
+            return;
+        }
+        AutoCompletionList list = new AutoCompletionList();
+        layer.data.getAutoCompletionManager().populateWithTagValues(list, keys);
+        field.setAutoCompletionList(list);
+    }
+
+    /**
+     * Called by {@link TaggingPreset#createPanel} during tagging preset panel creation.
+     * All components defining this tagging preset item must be added to given panel.
+     *
+     * @param p The panel where components must be added
+     * @param sel The related selected OSM primitives
+     * @param presetInitiallyMatches Whether this {@link TaggingPreset} already matched before applying,
+     *                               i.e. whether the map feature already existed on the primitive.
+     * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise.
+     */
+    protected abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches);
+
+    /**
+     * Adds the new tags to apply to selected OSM primitives when the preset holding this item is applied.
+     * @param changedTags The list of changed tags to modify if needed
+     */
+    protected abstract void addCommands(List<Tag> changedTags);
+
+    /**
+     * Tests whether the tags match this item.
+     * Note that for a match, at least one positive and no negative is required.
+     * @param tags the tags of an {@link OsmPrimitive}
+     * @return {@code true} if matches (positive), {@code null} if neutral, {@code false} if mismatches (negative).
+     */
+    protected Boolean matches(Map<String, String> tags) {
+        return null;
+    }
+
+    protected static Set<TaggingPresetType> getType(String types) throws SAXException {
+        if (types == null || types.isEmpty()) {
+            throw new SAXException(tr("Unknown type: {0}", types));
+        }
+        if (TYPE_CACHE.containsKey(types))
+            return TYPE_CACHE.get(types);
+        Set<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class);
+        for (String type : Arrays.asList(types.split(","))) {
+            try {
+                TaggingPresetType presetType = TaggingPresetType.fromString(type);
+                result.add(presetType);
+            } catch (IllegalArgumentException e) {
+                throw new SAXException(tr("Unknown type: {0}", type), e);
+            }
+        }
+        TYPE_CACHE.put(types, result);
+        return result;
+    }
+
+    protected static String fixPresetString(String s) {
+        return s == null ? s : s.replaceAll("'", "''");
+    }
+
+    protected static String getLocaleText(String text, String text_context, String defaultText) {
+        if (text == null) {
+            return defaultText;
+        } else if (text_context != null) {
+            return trc(text_context, fixPresetString(text));
+        } else {
+            return tr(fixPresetString(text));
+        }
+    }
+
+    protected static Integer parseInteger(String str) {
+        if (str == null || str.isEmpty())
+            return null;
+        try {
+            return Integer.valueOf(str);
+        } catch (Exception e) {
+            if (Main.isTraceEnabled()) {
+                Main.trace(e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    protected static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) {
+        final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
+        ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true);
+        if (maxSize != null) {
+            imgProv.setMaxSize(maxSize);
+        }
+        return imgProv.get();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetLabel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetLabel.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetLabel.java	(revision 8863)
@@ -0,0 +1,73 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.font.TextAttribute;
+import java.util.Collections;
+
+import javax.swing.JLabel;
+
+public class TaggingPresetLabel extends JLabel {
+
+    protected final TaggingPreset t;
+
+    /**
+     * Constructs a new {@code PresetLabel}.
+     * @param t the tagging preset
+     */
+    public TaggingPresetLabel(TaggingPreset t) {
+        super(t.getName() + " …");
+        setIcon(t.getIcon());
+        addMouseListener(new PresetLabelMouseListener(this));
+        this.t = t;
+    }
+
+    /**
+     * Small helper class that manages the highlighting of the label on hover as well as opening
+     * the corresponding preset when clicked
+     */
+    public static class PresetLabelMouseListener implements MouseListener {
+        protected final JLabel label;
+        protected final Font hover;
+        protected final Font normal;
+
+        /**
+         * Constructs a new {@code PresetLabelMouseListener}.
+         * @param lbl Label to highlight
+         */
+        public PresetLabelMouseListener(JLabel lbl) {
+            label = lbl;
+            lbl.setCursor(new Cursor(Cursor.HAND_CURSOR));
+            normal = label.getFont();
+            hover = normal.deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED));
+        }
+
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            // Do nothing
+        }
+
+        @Override
+        public void mouseEntered(MouseEvent e) {
+            label.setFont(hover);
+        }
+
+        @Override
+        public void mouseExited(MouseEvent e) {
+            label.setFont(normal);
+        }
+
+        @Override
+        public void mousePressed(MouseEvent e) {
+            // Do nothing
+        }
+
+        @Override
+        public void mouseReleased(MouseEvent e) {
+            // Do nothing
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetListener.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetListener.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetListener.java	(revision 8863)
@@ -0,0 +1,14 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+/**
+ * Notification of tagging presets events.
+ * @since 7100
+ */
+public interface TaggingPresetListener {
+
+    /**
+     * Called after list of tagging presets has been modified.
+     */
+    void taggingPresetsModified();
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java	(revision 8863)
@@ -0,0 +1,157 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.swing.Action;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.AlphanumComparator;
+
+public class TaggingPresetMenu extends TaggingPreset {
+    public JMenu menu; // set by TaggingPresets
+
+    private static class PresetTextComparator implements Comparator<JMenuItem>, Serializable {
+        @Override
+        public int compare(JMenuItem o1, JMenuItem o2) {
+            if (Main.main.menu.presetSearchAction.equals(o1.getAction()))
+                return -1;
+            else if (Main.main.menu.presetSearchAction.equals(o2.getAction()))
+                return 1;
+            else
+                return AlphanumComparator.getInstance().compare(o1.getText(), o2.getText());
+        }
+    }
+
+    /**
+     * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TaggingPresetMenu that = (TaggingPresetMenu) o;
+        return Objects.equals(getRawName(), that.getRawName());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getRawName());
+    }
+
+    @Override
+    public void setDisplayName() {
+        putValue(Action.NAME, getName());
+        /** Tooltips should be shown for the toolbar buttons, but not in the menu. */
+        putValue(OPTIONAL_TOOLTIP_TEXT, group != null ?
+                tr("Preset group {1} / {0}", getLocaleName(), group.getName()) :
+                    tr("Preset group {0}", getLocaleName()));
+        putValue("toolbar", "tagginggroup_" + getRawName());
+    }
+
+    private Component copyMenuComponent(Component menuComponent) {
+        if (menuComponent instanceof JMenu) {
+            JMenu menu = (JMenu) menuComponent;
+            JMenu result = new JMenu(menu.getAction());
+            for (Component item:menu.getMenuComponents()) {
+                result.add(copyMenuComponent(item));
+            }
+            result.setText(menu.getText());
+            return result;
+        } else if (menuComponent instanceof JMenuItem) {
+            JMenuItem menuItem = (JMenuItem) menuComponent;
+            JMenuItem result = new JMenuItem(menuItem.getAction());
+            result.setText(menuItem.getText());
+            return result;
+        } else if (menuComponent instanceof JSeparator) {
+            return new JSeparator();
+        } else {
+            return menuComponent;
+        }
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Object s = e.getSource();
+        if (menu != null && s instanceof Component) {
+            JPopupMenu pm = new JPopupMenu(getName());
+            for (Component c : menu.getMenuComponents()) {
+                pm.add(copyMenuComponent(c));
+            }
+            Point p = MouseInfo.getPointerInfo().getLocation();
+            pm.show(Main.parent, p.x-Main.parent.getX(), p.y-Main.parent.getY());
+        }
+    }
+
+    /**
+     * Sorts the menu items using the translated item text
+     */
+    public void sortMenu() {
+        TaggingPresetMenu.sortMenu(this.menu);
+    }
+
+    /**
+     * Sorts the menu items using the translated item text
+     */
+    public static void sortMenu(JMenu menu) {
+        Component[] items = menu.getMenuComponents();
+        PresetTextComparator comp = new PresetTextComparator();
+        List<JMenuItem> sortarray = new ArrayList<>();
+        int lastSeparator = 0;
+        for (int i = 0; i < items.length; i++) {
+            Object item = items[i];
+            if (item instanceof JMenu) {
+                sortMenu((JMenu) item);
+            }
+            if (item instanceof JMenuItem) {
+                sortarray.add((JMenuItem) item);
+                if (i == items.length-1) {
+                    Collections.sort(sortarray, comp);
+                    int pos = 0;
+                    for (JMenuItem menuItem : sortarray) {
+                        int oldPos;
+                        if (lastSeparator == 0) {
+                            oldPos = pos;
+                        } else {
+                            oldPos = pos+lastSeparator+1;
+                        }
+                        menu.add(menuItem, oldPos);
+                        pos++;
+                    }
+                    sortarray = new ArrayList<>();
+                    lastSeparator = 0;
+                }
+            } else if (item instanceof JSeparator) {
+                Collections.sort(sortarray, comp);
+                int pos = 0;
+                for (JMenuItem menuItem : sortarray) {
+                    int oldPos;
+                    if (lastSeparator == 0) {
+                        oldPos = pos;
+                    } else {
+                        oldPos = pos+lastSeparator+1;
+                    }
+                    menu.add(menuItem, oldPos);
+                    pos++;
+                }
+                sortarray = new ArrayList<>();
+                lastSeparator = i;
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetNameTemplateList.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetNameTemplateList.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetNameTemplateList.java	(revision 8863)
@@ -0,0 +1,78 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+
+/**
+ * List of tagging presets with name templates, allows to find appropriate template based on existing primitive
+ */
+public final class TaggingPresetNameTemplateList implements TaggingPresetListener {
+
+    private static TaggingPresetNameTemplateList instance;
+
+    private final List<TaggingPreset> presetsWithPattern = new LinkedList<>();
+
+    /**
+     * Replies the unique instance.
+     * @return the unique instance
+     */
+    public static synchronized TaggingPresetNameTemplateList getInstance() {
+        if (instance == null) {
+            instance = new TaggingPresetNameTemplateList();
+            TaggingPresets.addListener(instance);
+        }
+        return instance;
+    }
+
+    private TaggingPresetNameTemplateList() {
+        buildPresetsWithPattern();
+    }
+
+    private void buildPresetsWithPattern() {
+        synchronized (this) {
+            Main.debug("Building list of presets with name template");
+            presetsWithPattern.clear();
+            for (TaggingPreset tp : TaggingPresets.getTaggingPresets()) {
+                if (tp.nameTemplate != null) {
+                    presetsWithPattern.add(tp);
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds and returns the first occurence of preset with template name matching the given primitive
+     * @param primitive The primitive to match
+     * @return the first occurence of preset with template name matching the primitive
+     */
+    public TaggingPreset findPresetTemplate(OsmPrimitive primitive) {
+        synchronized (this) {
+            for (TaggingPreset t : presetsWithPattern) {
+                Collection<TaggingPresetType> type = Collections.singleton(TaggingPresetType.forPrimitive(primitive));
+                if (t.typeMatches(type)) {
+                    if (t.nameTemplateFilter != null) {
+                        if (t.nameTemplateFilter.match(primitive))
+                            return t;
+                        else {
+                            continue;
+                        }
+                    } else if (t.matches(type, primitive.getKeys(), false)) {
+                        return t;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void taggingPresetsModified() {
+        buildPresetsWithPattern();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java	(revision 8863)
@@ -0,0 +1,420 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
+import org.openstreetmap.josm.gui.tagging.presets.items.Check;
+import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
+import org.openstreetmap.josm.gui.tagging.presets.items.Combo;
+import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;
+import org.openstreetmap.josm.gui.tagging.presets.items.ItemSeparator;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.Label;
+import org.openstreetmap.josm.gui.tagging.presets.items.Link;
+import org.openstreetmap.josm.gui.tagging.presets.items.MultiSelect;
+import org.openstreetmap.josm.gui.tagging.presets.items.Optional;
+import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
+import org.openstreetmap.josm.gui.tagging.presets.items.Space;
+import org.openstreetmap.josm.gui.tagging.presets.items.Text;
+import org.openstreetmap.josm.io.CachedFile;
+import org.openstreetmap.josm.io.UTFInputStreamReader;
+import org.openstreetmap.josm.tools.Predicates;
+import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.tools.XmlObjectParser;
+import org.xml.sax.SAXException;
+
+/**
+ * The tagging presets reader.
+ * @since 6068
+ */
+public final class TaggingPresetReader {
+
+    /**
+     * The accepted MIME types sent in the HTTP Accept header.
+     * @since 6867
+     */
+    public static final String PRESET_MIME_TYPES =
+            "application/xml, text/xml, text/plain; q=0.8, application/zip, application/octet-stream; q=0.5";
+
+    private static volatile File zipIcons;
+    private static volatile boolean loadIcons = true;
+
+    /**
+     * Holds a reference to a chunk of items/objects.
+     */
+    public static class Chunk {
+        /** The chunk id, can be referenced later */
+        public String id;
+    }
+
+    /**
+     * Holds a reference to an earlier item/object.
+     */
+    public static class Reference {
+        /** Reference matching a chunk id defined earlier **/
+        public String ref;
+    }
+
+    static class HashSetWithLast<E> extends LinkedHashSet<E> {
+        protected E last;
+
+        @Override
+        public boolean add(E e) {
+            last = e;
+            return super.add(e);
+        }
+
+        /**
+         * Returns the last inserted element.
+         */
+        public E getLast() {
+            return last;
+        }
+    }
+
+    /**
+     * Returns the set of preset source URLs.
+     * @return The set of preset source URLs.
+     */
+    public static Set<String> getPresetSources() {
+        return new TaggingPresetPreference.PresetPrefHelper().getActiveUrls();
+    }
+
+    private static XmlObjectParser buildParser() {
+        XmlObjectParser parser = new XmlObjectParser();
+        parser.mapOnStart("item", TaggingPreset.class);
+        parser.mapOnStart("separator", TaggingPresetSeparator.class);
+        parser.mapBoth("group", TaggingPresetMenu.class);
+        parser.map("text", Text.class);
+        parser.map("link", Link.class);
+        parser.map("preset_link", PresetLink.class);
+        parser.mapOnStart("optional", Optional.class);
+        parser.mapOnStart("roles", Roles.class);
+        parser.map("role", Role.class);
+        parser.map("checkgroup", CheckGroup.class);
+        parser.map("check", Check.class);
+        parser.map("combo", Combo.class);
+        parser.map("multiselect", MultiSelect.class);
+        parser.map("label", Label.class);
+        parser.map("space", Space.class);
+        parser.map("key", Key.class);
+        parser.map("list_entry", ComboMultiSelect.PresetListEntry.class);
+        parser.map("item_separator", ItemSeparator.class);
+        parser.mapBoth("chunk", Chunk.class);
+        parser.map("reference", Reference.class);
+        return parser;
+    }
+
+    /**
+     * Reads all tagging presets from the input reader.
+     * @param in The input reader
+     * @param validate if {@code true}, XML validation will be performed
+     * @return collection of tagging presets
+     * @throws SAXException if any XML error occurs
+     */
+    public static Collection<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
+        return readAll(in, validate, new HashSetWithLast<TaggingPreset>());
+    }
+
+    /**
+     * Reads all tagging presets from the input reader.
+     * @param in The input reader
+     * @param validate if {@code true}, XML validation will be performed
+     * @param all the accumulator for parsed tagging presets
+     * @return the accumulator
+     * @throws SAXException if any XML error occurs
+     */
+    static Collection<TaggingPreset> readAll(Reader in, boolean validate, HashSetWithLast<TaggingPreset> all) throws SAXException {
+        XmlObjectParser parser = buildParser();
+
+        /** to detect end of {@code <group>} */
+        TaggingPresetMenu lastmenu = null;
+        /** to detect end of reused {@code <group>} */
+        TaggingPresetMenu lastmenuOriginal = null;
+        Roles lastrole = null;
+        final List<Check> checks = new LinkedList<>();
+        List<ComboMultiSelect.PresetListEntry> listEntries = new LinkedList<>();
+        final Map<String, List<Object>> byId = new HashMap<>();
+        final Deque<String> lastIds = new ArrayDeque<>();
+        /** lastIdIterators contains non empty iterators of items to be handled before obtaining the next item from the XML parser */
+        final Deque<Iterator<Object>> lastIdIterators = new ArrayDeque<>();
+
+        if (validate) {
+            parser.startWithValidation(in, Main.getXMLBase()+"/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
+        } else {
+            parser.start(in);
+        }
+        while (parser.hasNext() || !lastIdIterators.isEmpty()) {
+            final Object o;
+            if (!lastIdIterators.isEmpty()) {
+                // obtain elements from lastIdIterators with higher priority
+                o = lastIdIterators.peek().next();
+                if (!lastIdIterators.peek().hasNext()) {
+                    // remove iterator if is empty
+                    lastIdIterators.pop();
+                }
+            } else {
+                o = parser.next();
+            }
+            if (o instanceof Chunk) {
+                if (!lastIds.isEmpty() && ((Chunk) o).id.equals(lastIds.peek())) {
+                    // pop last id on end of object, don't process further
+                    lastIds.pop();
+                    ((Chunk) o).id = null;
+                    continue;
+                } else {
+                    // if preset item contains an id, store a mapping for later usage
+                    String lastId = ((Chunk) o).id;
+                    lastIds.push(lastId);
+                    byId.put(lastId, new ArrayList<>());
+                    continue;
+                }
+            } else if (!lastIds.isEmpty()) {
+                // add object to mapping for later usage
+                byId.get(lastIds.peek()).add(o);
+                continue;
+            }
+            if (o instanceof Reference) {
+                // if o is a reference, obtain the corresponding objects from the mapping,
+                // and iterate over those before consuming the next element from parser.
+                final String ref = ((Reference) o).ref;
+                if (byId.get(ref) == null) {
+                    throw new SAXException(tr("Reference {0} is being used before it was defined", ref));
+                }
+                Iterator<Object> it = byId.get(ref).iterator();
+                if (it.hasNext()) {
+                    lastIdIterators.push(it);
+                } else {
+                    Main.warn("Ignoring reference '"+ref+"' denoting an empty chunk");
+                }
+                continue;
+            }
+            if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) {
+                all.getLast().data.addAll(checks);
+                checks.clear();
+            }
+            if (o instanceof TaggingPresetMenu) {
+                TaggingPresetMenu tp = (TaggingPresetMenu) o;
+                if (tp == lastmenu || tp == lastmenuOriginal) {
+                    lastmenu = tp.group;
+                } else {
+                    tp.group = lastmenu;
+                    if (all.contains(tp)) {
+                        lastmenuOriginal = tp;
+                        tp = (TaggingPresetMenu) Utils.filter(all, Predicates.<TaggingPreset>equalTo(tp)).iterator().next();
+                        lastmenuOriginal.group = null;
+                    } else {
+                        tp.setDisplayName();
+                        all.add(tp);
+                        lastmenuOriginal = null;
+                    }
+                    lastmenu = tp;
+                }
+                lastrole = null;
+            } else if (o instanceof TaggingPresetSeparator) {
+                TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
+                tp.group = lastmenu;
+                all.add(tp);
+                lastrole = null;
+            } else if (o instanceof TaggingPreset) {
+                TaggingPreset tp = (TaggingPreset) o;
+                tp.group = lastmenu;
+                tp.setDisplayName();
+                all.add(tp);
+                lastrole = null;
+            } else {
+                if (!all.isEmpty()) {
+                    if (o instanceof Roles) {
+                        all.getLast().data.add((TaggingPresetItem) o);
+                        if (all.getLast().roles != null) {
+                            throw new SAXException(tr("Roles cannot appear more than once"));
+                        }
+                        all.getLast().roles = (Roles) o;
+                        lastrole = (Roles) o;
+                    } else if (o instanceof Role) {
+                        if (lastrole == null)
+                            throw new SAXException(tr("Preset role element without parent"));
+                        lastrole.roles.add((Role) o);
+                    } else if (o instanceof Check) {
+                        checks.add((Check) o);
+                    } else if (o instanceof ComboMultiSelect.PresetListEntry) {
+                        listEntries.add((ComboMultiSelect.PresetListEntry) o);
+                    } else if (o instanceof CheckGroup) {
+                        all.getLast().data.add((TaggingPresetItem) o);
+                        // Make sure list of checks is empty to avoid adding checks several times
+                        // when used in chunks (fix #10801)
+                        ((CheckGroup) o).checks.clear();
+                        ((CheckGroup) o).checks.addAll(checks);
+                        checks.clear();
+                    } else {
+                        if (!checks.isEmpty()) {
+                            all.getLast().data.addAll(checks);
+                            checks.clear();
+                        }
+                        all.getLast().data.add((TaggingPresetItem) o);
+                        if (o instanceof ComboMultiSelect) {
+                            ((ComboMultiSelect) o).addListEntries(listEntries);
+                        } else if (o instanceof Key) {
+                            if (((Key) o).value == null) {
+                                ((Key) o).value = ""; // Fix #8530
+                            }
+                        }
+                        listEntries = new LinkedList<>();
+                        lastrole = null;
+                    }
+                } else
+                    throw new SAXException(tr("Preset sub element without parent"));
+            }
+        }
+        if (!all.isEmpty() && !checks.isEmpty()) {
+            all.getLast().data.addAll(checks);
+            checks.clear();
+        }
+        return all;
+    }
+
+    /**
+     * Reads all tagging presets from the given source.
+     * @param source a given filename, URL or internal resource
+     * @param validate if {@code true}, XML validation will be performed
+     * @return collection of tagging presets
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
+        return readAll(source, validate, new HashSetWithLast<TaggingPreset>());
+    }
+
+    /**
+     * Reads all tagging presets from the given source.
+     * @param source a given filename, URL or internal resource
+     * @param validate if {@code true}, XML validation will be performed
+     * @param all the accumulator for parsed tagging presets
+     * @return the accumulator
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    static Collection<TaggingPreset> readAll(String source, boolean validate, HashSetWithLast<TaggingPreset> all)
+            throws SAXException, IOException {
+        Collection<TaggingPreset> tp;
+        CachedFile cf = new CachedFile(source).setHttpAccept(PRESET_MIME_TYPES);
+        try (
+            // zip may be null, but Java 7 allows it: https://blogs.oracle.com/darcy/entry/project_coin_null_try_with
+            InputStream zip = cf.findZipEntryInputStream("xml", "preset")
+        ) {
+            if (zip != null) {
+                zipIcons = cf.getFile();
+            }
+            try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) {
+                tp = readAll(new BufferedReader(r), validate, all);
+            }
+        }
+        return tp;
+    }
+
+    /**
+     * Reads all tagging presets from the given sources.
+     * @param sources Collection of tagging presets sources.
+     * @param validate if {@code true}, presets will be validated against XML schema
+     * @return Collection of all presets successfully read
+     */
+    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
+        return readAll(sources, validate, true);
+    }
+
+    /**
+     * Reads all tagging presets from the given sources.
+     * @param sources Collection of tagging presets sources.
+     * @param validate if {@code true}, presets will be validated against XML schema
+     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
+     * @return Collection of all presets successfully read
+     */
+    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate, boolean displayErrMsg) {
+        HashSetWithLast<TaggingPreset> allPresets = new HashSetWithLast<>();
+        for (String source : sources)  {
+            try {
+                readAll(source, validate, allPresets);
+            } catch (IOException e) {
+                Main.error(e, false);
+                Main.error(source);
+                if (source.startsWith("http")) {
+                    Main.addNetworkError(source, e);
+                }
+                if (displayErrMsg) {
+                    JOptionPane.showMessageDialog(
+                            Main.parent,
+                            tr("Could not read tagging preset source: {0}", source),
+                            tr("Error"),
+                            JOptionPane.ERROR_MESSAGE
+                            );
+                }
+            } catch (SAXException e) {
+                Main.error(e);
+                Main.error(source);
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        "<html>" + tr("Error parsing {0}: ", source) + "<br><br><table width=600>" + e.getMessage() + "</table></html>",
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE
+                        );
+            }
+        }
+        return allPresets;
+    }
+
+    /**
+     * Reads all tagging presets from sources stored in preferences.
+     * @param validate if {@code true}, presets will be validated against XML schema
+     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
+     * @return Collection of all presets successfully read
+     */
+    public static Collection<TaggingPreset> readFromPreferences(boolean validate, boolean displayErrMsg) {
+        return readAll(getPresetSources(), validate, displayErrMsg);
+    }
+
+    public static File getZipIcons() {
+        return zipIcons;
+    }
+
+    /**
+     * Returns true if icon images should be loaded.
+     */
+    public static boolean isLoadIcons() {
+        return loadIcons;
+    }
+
+    /**
+     * Sets whether icon images should be loaded.
+     */
+    public static void setLoadIcons(boolean loadIcons) {
+        TaggingPresetReader.loadIcons = loadIcons;
+    }
+
+    private TaggingPresetReader() {
+        // Hide default constructor for utils classes
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchAction.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchAction.java	(revision 8863)
@@ -0,0 +1,37 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * The tagging presets search action (F3).
+ * @since 3388
+ */
+public class TaggingPresetSearchAction extends JosmAction {
+
+    /**
+     * Constructs a new {@code TaggingPresetSearchAction}.
+     */
+    public TaggingPresetSearchAction() {
+        super(tr("Search preset"), "dialogs/search", tr("Show preset search dialog"),
+                Shortcut.registerShortcut("preset:search", tr("Search presets"), KeyEvent.VK_F3, Shortcut.DIRECT), false);
+        putValue("toolbar", "presets/search");
+        Main.toolbar.register(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+        if (!Main.main.hasEditLayer())
+            return;
+
+        TaggingPresetSearchDialog.getInstance().showDialog();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchDialog.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchDialog.java	(revision 8863)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+
+/**
+ * The tagging presets search dialog (F3).
+ * @since 3388
+ */
+public final class TaggingPresetSearchDialog extends ExtendedDialog {
+
+    private static TaggingPresetSearchDialog instance;
+
+    private TaggingPresetSelector selector;
+
+    /**
+     * Returns the unique instance of {@code TaggingPresetSearchDialog}.
+     * @return the unique instance of {@code TaggingPresetSearchDialog}.
+     */
+    public static synchronized TaggingPresetSearchDialog getInstance() {
+        if (instance == null) {
+            instance = new TaggingPresetSearchDialog();
+        }
+        return instance;
+    }
+
+    private TaggingPresetSearchDialog() {
+        super(Main.parent, tr("Presets"), new String[] {tr("Select"), tr("Cancel")});
+        selector = new TaggingPresetSelector(true, true);
+        setContent(selector);
+        DataSet.addSelectionListener(selector);
+        selector.setDblClickListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                buttonAction(0, null);
+            }
+        });
+    }
+
+    @Override
+    public ExtendedDialog showDialog() {
+        selector.init();
+        super.showDialog();
+        selector.clearSelection();
+        return this;
+    }
+
+    @Override
+    protected void buttonAction(int buttonIndex, ActionEvent evt) {
+        super.buttonAction(buttonIndex, evt);
+        if (buttonIndex == 0) {
+            TaggingPreset preset = selector.getSelectedPreset();
+            if (preset != null) {
+                preset.actionPerformed(null);
+            }
+        }
+        selector.savePreferences();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchPrimitiveDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchPrimitiveDialog.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchPrimitiveDialog.java	(revision 8863)
@@ -0,0 +1,95 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * A dialog that allows to select a preset and then selects all matching OSM objects.
+ * @see org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchDialog
+ */
+public final class TaggingPresetSearchPrimitiveDialog extends ExtendedDialog {
+
+    private static TaggingPresetSearchPrimitiveDialog instance;
+
+    private TaggingPresetSelector selector;
+
+    /**
+     * An action executing {@link TaggingPresetSearchPrimitiveDialog}.
+     */
+    public static class Action extends JosmAction {
+
+        /**
+         * Constructs a new {@link TaggingPresetSearchPrimitiveDialog.Action}.
+         */
+        public Action() {
+            super(tr("Search for objects by preset"), "dialogs/search", tr("Show preset search dialog"),
+                    Shortcut.registerShortcut("preset:search-objects", tr("Search for objects by preset"), KeyEvent.VK_F3, Shortcut.SHIFT),
+                    false);
+            putValue("toolbar", "presets/search-objects");
+            Main.toolbar.register(this);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            if (Main.main.hasEditLayer()) {
+                TaggingPresetSearchPrimitiveDialog.getInstance().showDialog();
+            }
+        }
+    }
+
+    /**
+     * Returns the unique instance of {@code TaggingPresetSearchPrimitiveDialog}.
+     * @return the unique instance of {@code TaggingPresetSearchPrimitiveDialog}.
+     */
+    public static synchronized TaggingPresetSearchPrimitiveDialog getInstance() {
+        if (instance == null) {
+            instance = new TaggingPresetSearchPrimitiveDialog();
+        }
+        return instance;
+    }
+
+    TaggingPresetSearchPrimitiveDialog() {
+        super(Main.parent, tr("Presets"), new String[] {tr("Search"), tr("Cancel")});
+        selector = new TaggingPresetSelector(false, false);
+        setContent(selector);
+        selector.setDblClickListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                buttonAction(0, null);
+            }
+        });
+    }
+
+    @Override
+    public ExtendedDialog showDialog() {
+        selector.init();
+        super.showDialog();
+        selector.clearSelection();
+        return this;
+    }
+
+    @Override
+    protected void buttonAction(int buttonIndex, ActionEvent evt) {
+        super.buttonAction(buttonIndex, evt);
+        if (buttonIndex == 0) {
+            TaggingPreset preset = selector.getSelectedPreset();
+            if (preset != null) {
+                final Set<OsmPrimitive> matching = new HashSet<>(Utils.filter(Main.main.getCurrentDataSet().allPrimitives(), preset));
+                Main.main.getCurrentDataSet().setSelected(matching);
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java	(revision 8863)
@@ -0,0 +1,577 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractListModel;
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.ListCellRenderer;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
+import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
+import org.openstreetmap.josm.tools.Predicate;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * GUI component to select tagging preset: the list with filter and two checkboxes
+ * @since 6068
+ */
+public class TaggingPresetSelector extends JPanel implements SelectionChangedListener {
+
+    private static final int CLASSIFICATION_IN_FAVORITES = 300;
+    private static final int CLASSIFICATION_NAME_MATCH = 300;
+    private static final int CLASSIFICATION_GROUP_MATCH = 200;
+    private static final int CLASSIFICATION_TAGS_MATCH = 100;
+
+    private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
+    private static final BooleanProperty ONLY_APPLICABLE  = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
+
+    private final JosmTextField edSearchText;
+    private final JList<TaggingPreset> lsResult;
+    private final JCheckBox ckOnlyApplicable;
+    private final JCheckBox ckSearchInTags;
+    private final Set<TaggingPresetType> typesInSelection = EnumSet.noneOf(TaggingPresetType.class);
+    private boolean typesInSelectionDirty = true;
+    private final transient PresetClassifications classifications = new PresetClassifications();
+    private final ResultListModel lsResultModel = new ResultListModel();
+
+    private final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
+
+    private transient ActionListener dblClickListener;
+    private transient ActionListener clickListener;
+
+    private static class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
+        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
+        @Override
+        public Component getListCellRendererComponent(JList<? extends TaggingPreset> list, TaggingPreset tp, int index,
+                boolean isSelected, boolean cellHasFocus) {
+            JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus);
+            result.setText(tp.getName());
+            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
+            return result;
+        }
+    }
+
+    private static class ResultListModel extends AbstractListModel<TaggingPreset> {
+
+        private transient List<PresetClassification> presets = new ArrayList<>();
+
+        public synchronized void setPresets(List<PresetClassification> presets) {
+            this.presets = presets;
+            fireContentsChanged(this, 0, Integer.MAX_VALUE);
+        }
+
+        @Override
+        public synchronized TaggingPreset getElementAt(int index) {
+            return presets.get(index).preset;
+        }
+
+        @Override
+        public synchronized int getSize() {
+            return presets.size();
+        }
+
+        public synchronized boolean isEmpty() {
+            return presets.isEmpty();
+        }
+    }
+
+    /**
+     * Computes the match ration of a {@link TaggingPreset} wrt. a searchString.
+     */
+    public static class PresetClassification implements Comparable<PresetClassification> {
+        public final TaggingPreset preset;
+        public int classification;
+        public int favoriteIndex;
+        private final Collection<String> groups = new HashSet<>();
+        private final Collection<String> names = new HashSet<>();
+        private final Collection<String> tags = new HashSet<>();
+
+        PresetClassification(TaggingPreset preset) {
+            this.preset = preset;
+            TaggingPreset group = preset.group;
+            while (group != null) {
+                Collections.addAll(groups, group.getLocaleName().toLowerCase(Locale.ENGLISH).split("\\s"));
+                group = group.group;
+            }
+            Collections.addAll(names, preset.getLocaleName().toLowerCase(Locale.ENGLISH).split("\\s"));
+            for (TaggingPresetItem item: preset.data) {
+                if (item instanceof KeyedItem) {
+                    tags.add(((KeyedItem) item).key);
+                    if (item instanceof ComboMultiSelect) {
+                        final ComboMultiSelect cms = (ComboMultiSelect) item;
+                        if (Boolean.parseBoolean(cms.values_searchable)) {
+                            tags.addAll(cms.getDisplayValues());
+                        }
+                    }
+                    if (item instanceof Key && ((Key) item).value != null) {
+                        tags.add(((Key) item).value);
+                    }
+                } else if (item instanceof Roles) {
+                    for (Role role : ((Roles) item).roles) {
+                        tags.add(role.key);
+                    }
+                }
+            }
+        }
+
+        private int isMatching(Collection<String> values, String[] searchString) {
+            int sum = 0;
+            for (String word: searchString) {
+                boolean found = false;
+                boolean foundFirst = false;
+                for (String value: values) {
+                    int index = value.toLowerCase(Locale.ENGLISH).indexOf(word);
+                    if (index == 0) {
+                        foundFirst = true;
+                        break;
+                    } else if (index > 0) {
+                        found = true;
+                    }
+                }
+                if (foundFirst) {
+                    sum += 2;
+                } else if (found) {
+                    sum += 1;
+                } else
+                    return 0;
+            }
+            return sum;
+        }
+
+        int isMatchingGroup(String[] words) {
+            return isMatching(groups, words);
+        }
+
+        int isMatchingName(String[] words) {
+            return isMatching(names, words);
+        }
+
+        int isMatchingTags(String[] words) {
+            return isMatching(tags, words);
+        }
+
+        @Override
+        public int compareTo(PresetClassification o) {
+            int result = o.classification - classification;
+            if (result == 0)
+                return preset.getName().compareTo(o.preset.getName());
+            else
+                return result;
+        }
+
+        @Override
+        public String toString() {
+            return classification + " " + preset;
+        }
+    }
+
+    /**
+     * Constructs a new {@code TaggingPresetSelector}.
+     */
+    public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
+        super(new BorderLayout());
+        classifications.loadPresets(TaggingPresets.getTaggingPresets());
+
+        edSearchText = new JosmTextField();
+        edSearchText.getDocument().addDocumentListener(new DocumentListener() {
+            @Override
+            public void removeUpdate(DocumentEvent e) {
+                filterPresets();
+            }
+
+            @Override
+            public void insertUpdate(DocumentEvent e) {
+                filterPresets();
+            }
+
+            @Override
+            public void changedUpdate(DocumentEvent e) {
+                filterPresets();
+            }
+        });
+        edSearchText.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyPressed(KeyEvent e) {
+                switch (e.getKeyCode()) {
+                case KeyEvent.VK_DOWN:
+                    selectPreset(lsResult.getSelectedIndex() + 1);
+                    break;
+                case KeyEvent.VK_UP:
+                    selectPreset(lsResult.getSelectedIndex() - 1);
+                    break;
+                case KeyEvent.VK_PAGE_DOWN:
+                    selectPreset(lsResult.getSelectedIndex() + 10);
+                    break;
+                case KeyEvent.VK_PAGE_UP:
+                    selectPreset(lsResult.getSelectedIndex() - 10);
+                    break;
+                case KeyEvent.VK_HOME:
+                    selectPreset(0);
+                    break;
+                case KeyEvent.VK_END:
+                    selectPreset(lsResultModel.getSize());
+                    break;
+                }
+            }
+        });
+        add(edSearchText, BorderLayout.NORTH);
+
+        lsResult = new JList<>(lsResultModel);
+        lsResult.setCellRenderer(new ResultListCellRenderer());
+        lsResult.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() > 1) {
+                    if (dblClickListener != null)
+                        dblClickListener.actionPerformed(null);
+                } else {
+                    if (clickListener != null)
+                        clickListener.actionPerformed(null);
+                }
+            }
+        });
+        add(new JScrollPane(lsResult), BorderLayout.CENTER);
+
+        JPanel pnChecks = new JPanel();
+        pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
+
+        if (displayOnlyApplicable) {
+            ckOnlyApplicable = new JCheckBox();
+            ckOnlyApplicable.setText(tr("Show only applicable to selection"));
+            pnChecks.add(ckOnlyApplicable);
+            ckOnlyApplicable.addItemListener(new ItemListener() {
+                @Override
+                public void itemStateChanged(ItemEvent e) {
+                    filterPresets();
+                }
+            });
+        } else {
+            ckOnlyApplicable = null;
+        }
+
+        if (displaySearchInTags) {
+            ckSearchInTags = new JCheckBox();
+            ckSearchInTags.setText(tr("Search in tags"));
+            ckSearchInTags.setSelected(SEARCH_IN_TAGS.get());
+            ckSearchInTags.addItemListener(new ItemListener() {
+                @Override
+                public void itemStateChanged(ItemEvent e) {
+                    filterPresets();
+                }
+            });
+            pnChecks.add(ckSearchInTags);
+        } else {
+            ckSearchInTags = null;
+        }
+
+        add(pnChecks, BorderLayout.SOUTH);
+
+        setPreferredSize(new Dimension(400, 300));
+        filterPresets();
+        JPopupMenu popupMenu = new JPopupMenu();
+        popupMenu.add(new AbstractAction(tr("Add toolbar button")) {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                String res = getSelectedPreset().getToolbarString();
+                Main.toolbar.addCustomButton(res, -1, false);
+            }
+        });
+        lsResult.addMouseListener(new PopupMenuLauncher(popupMenu));
+    }
+
+    private synchronized void selectPreset(int newIndex) {
+        if (newIndex < 0) {
+            newIndex = 0;
+        }
+        if (newIndex > lsResultModel.getSize() - 1) {
+            newIndex = lsResultModel.getSize() - 1;
+        }
+        lsResult.setSelectedIndex(newIndex);
+        lsResult.ensureIndexIsVisible(newIndex);
+    }
+
+    /**
+     * Search expression can be in form: "group1/group2/name" where names can contain multiple words
+     */
+    private synchronized void filterPresets() {
+        //TODO Save favorites to file
+        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
+        boolean onlyApplicable = ckOnlyApplicable != null && ckOnlyApplicable.isSelected();
+        boolean inTags = ckSearchInTags != null && ckSearchInTags.isSelected();
+
+        DataSet ds = Main.main.getCurrentDataSet();
+        Collection<OsmPrimitive> selected = (ds == null) ? Collections.<OsmPrimitive>emptyList() : ds.getSelected();
+        final List<PresetClassification> result = classifications.getMatchingPresets(
+                text, onlyApplicable, inTags, getTypesInSelection(), selected);
+
+        TaggingPreset oldPreset = getSelectedPreset();
+        lsResultModel.setPresets(result);
+        TaggingPreset newPreset = getSelectedPreset();
+        if (!Objects.equals(oldPreset, newPreset)) {
+            int[] indices = lsResult.getSelectedIndices();
+            for (ListSelectionListener listener : listSelectionListeners) {
+                listener.valueChanged(new ListSelectionEvent(lsResult, lsResult.getSelectedIndex(),
+                        indices.length > 0 ? indices[indices.length-1] : -1, false));
+            }
+        }
+    }
+
+    /**
+     * A collection of {@link PresetClassification}s with the functionality of filtering wrt. searchString.
+     */
+    public static class PresetClassifications implements Iterable<PresetClassification> {
+
+        private final List<PresetClassification> classifications = new ArrayList<>();
+
+        public List<PresetClassification> getMatchingPresets(String searchText, boolean onlyApplicable, boolean inTags,
+                Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
+            final String[] groupWords;
+            final String[] nameWords;
+
+            if (searchText.contains("/")) {
+                groupWords = searchText.substring(0, searchText.lastIndexOf('/')).split("[\\s/]");
+                nameWords = searchText.substring(searchText.indexOf('/') + 1).split("\\s");
+            } else {
+                groupWords = null;
+                nameWords = searchText.split("\\s");
+            }
+
+            return getMatchingPresets(groupWords, nameWords, onlyApplicable, inTags, presetTypes, selectedPrimitives);
+        }
+
+        public List<PresetClassification> getMatchingPresets(String[] groupWords, String[] nameWords, boolean onlyApplicable,
+                boolean inTags, Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
+
+            final List<PresetClassification> result = new ArrayList<>();
+            for (PresetClassification presetClassification : classifications) {
+                TaggingPreset preset = presetClassification.preset;
+                presetClassification.classification = 0;
+
+                if (onlyApplicable) {
+                    boolean suitable = preset.typeMatches(presetTypes);
+
+                    if (!suitable && preset.types.contains(TaggingPresetType.RELATION)
+                            && preset.roles != null && !preset.roles.roles.isEmpty()) {
+                        final Predicate<Role> memberExpressionMatchesOnePrimitive = new Predicate<Role>() {
+                            @Override
+                            public boolean evaluate(Role object) {
+                                return object.memberExpression != null
+                                        && Utils.exists(selectedPrimitives, object.memberExpression);
+                            }
+                        };
+                        suitable = Utils.exists(preset.roles.roles, memberExpressionMatchesOnePrimitive);
+                        // keep the preset to allow the creation of new relations
+                    }
+                    if (!suitable) {
+                        continue;
+                    }
+                }
+
+                if (groupWords != null && presetClassification.isMatchingGroup(groupWords) == 0) {
+                    continue;
+                }
+
+                int matchName = presetClassification.isMatchingName(nameWords);
+
+                if (matchName == 0) {
+                    if (groupWords == null) {
+                        int groupMatch = presetClassification.isMatchingGroup(nameWords);
+                        if (groupMatch > 0) {
+                            presetClassification.classification = CLASSIFICATION_GROUP_MATCH + groupMatch;
+                        }
+                    }
+                    if (presetClassification.classification == 0 && inTags) {
+                        int tagsMatch = presetClassification.isMatchingTags(nameWords);
+                        if (tagsMatch > 0) {
+                            presetClassification.classification = CLASSIFICATION_TAGS_MATCH + tagsMatch;
+                        }
+                    }
+                } else {
+                    presetClassification.classification = CLASSIFICATION_NAME_MATCH + matchName;
+                }
+
+                if (presetClassification.classification > 0) {
+                    presetClassification.classification += presetClassification.favoriteIndex;
+                    result.add(presetClassification);
+                }
+            }
+
+            Collections.sort(result);
+            return result;
+
+        }
+
+        public void clear() {
+            classifications.clear();
+        }
+
+        public void loadPresets(Collection<TaggingPreset> presets) {
+            for (TaggingPreset preset : presets) {
+                if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
+                    continue;
+                }
+                classifications.add(new PresetClassification(preset));
+            }
+        }
+
+        @Override
+        public Iterator<PresetClassification> iterator() {
+            return classifications.iterator();
+        }
+    }
+
+    private Set<TaggingPresetType> getTypesInSelection() {
+        if (typesInSelectionDirty) {
+            synchronized (typesInSelection) {
+                typesInSelectionDirty = false;
+                typesInSelection.clear();
+                if (Main.main == null || Main.main.getCurrentDataSet() == null) return typesInSelection;
+                for (OsmPrimitive primitive : Main.main.getCurrentDataSet().getSelected()) {
+                    typesInSelection.add(TaggingPresetType.forPrimitive(primitive));
+                }
+            }
+        }
+        return typesInSelection;
+    }
+
+    @Override
+    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+        typesInSelectionDirty = true;
+    }
+
+    public synchronized void init() {
+        if (ckOnlyApplicable != null) {
+            ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
+            ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
+        }
+        listSelectionListeners.clear();
+        edSearchText.setText("");
+        filterPresets();
+    }
+
+    public void init(Collection<TaggingPreset> presets) {
+        classifications.clear();
+        classifications.loadPresets(presets);
+        init();
+    }
+
+    public synchronized void clearSelection() {
+        lsResult.getSelectionModel().clearSelection();
+    }
+
+    /**
+     * Save checkbox values in preferences for future reuse
+     */
+    public void savePreferences() {
+        if (ckSearchInTags != null) {
+            SEARCH_IN_TAGS.put(ckSearchInTags.isSelected());
+        }
+        if (ckOnlyApplicable != null && ckOnlyApplicable.isEnabled()) {
+            ONLY_APPLICABLE.put(ckOnlyApplicable.isSelected());
+        }
+    }
+
+    /**
+     * Determines, which preset is selected at the current moment
+     * @return selected preset (as action)
+     */
+    public synchronized TaggingPreset getSelectedPreset() {
+        if (lsResultModel.isEmpty()) return null;
+        int idx = lsResult.getSelectedIndex();
+        if (idx < 0 || idx >= lsResultModel.getSize()) {
+            idx = 0;
+        }
+        TaggingPreset preset = lsResultModel.getElementAt(idx);
+        for (PresetClassification pc: classifications) {
+            if (pc.preset == preset) {
+                pc.favoriteIndex = CLASSIFICATION_IN_FAVORITES;
+            } else if (pc.favoriteIndex > 0) {
+                pc.favoriteIndex--;
+            }
+        }
+        return preset;
+    }
+
+    public synchronized void setSelectedPreset(TaggingPreset p) {
+        lsResult.setSelectedValue(p, true);
+    }
+
+    public synchronized int getItemCount() {
+        return lsResultModel.getSize();
+    }
+
+    public void setDblClickListener(ActionListener dblClickListener) {
+        this.dblClickListener = dblClickListener;
+    }
+
+    public void setClickListener(ActionListener clickListener) {
+        this.clickListener = clickListener;
+    }
+
+    /**
+     * Adds a selection listener to the presets list.
+     * @param selectListener The list selection listener
+     * @since 7412
+     */
+    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
+        lsResult.getSelectionModel().addListSelectionListener(selectListener);
+        listSelectionListeners.add(selectListener);
+    }
+
+    /**
+     * Removes a selection listener from the presets list.
+     * @param selectListener The list selection listener
+     * @since 7412
+     */
+    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
+        listSelectionListeners.remove(selectListener);
+        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSeparator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSeparator.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSeparator.java	(revision 8863)
@@ -0,0 +1,11 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+/**
+ * Class used to represent a {@link javax.swing.JSeparator} inside tagging preset menu.
+ * @since 895
+ */
+public class TaggingPresetSeparator extends TaggingPreset {
+    @Override
+    public void setDisplayName() {}
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetType.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetType.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetType.java	(revision 8863)
@@ -0,0 +1,80 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+
+/**
+ * Enumeration of OSM primitive types associated with names and icons
+ * @since 6068
+ */
+public enum TaggingPresetType {
+    /** Node */
+    NODE(/* ICON */ "Mf_node", "node"),
+    /** Way */
+    WAY(/* ICON */ "Mf_way", "way"),
+    /** Relation */
+    RELATION(/* ICON */ "Mf_relation", "relation"),
+    /** Closed way */
+    CLOSEDWAY(/* ICON */ "Mf_closedway", "closedway");
+    private final String iconName;
+    private final String name;
+
+    TaggingPresetType(String iconName, String name) {
+        this.iconName = iconName + ".svg";
+        this.name = name;
+    }
+
+    /**
+     * Replies the SVG icon name.
+     * @return the SVG icon name
+     */
+    public String getIconName() {
+        return iconName;
+    }
+
+    /**
+     * Replies the name, as used in XML presets.
+     * @return the name: "node", "way", "relation" or "closedway"
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Determines the {@code TaggingPresetType} of a given primitive.
+     * @param p The OSM primitive
+     * @return the {@code TaggingPresetType} of {@code p}
+     */
+    public static TaggingPresetType forPrimitive(OsmPrimitive p) {
+        return forPrimitiveType(p.getDisplayType());
+    }
+
+    /**
+     * Determines the {@code TaggingPresetType} of a given primitive type.
+     * @param type The OSM primitive type
+     * @return the {@code TaggingPresetType} of {@code type}
+     */
+    public static TaggingPresetType forPrimitiveType(OsmPrimitiveType type) {
+        if (type == OsmPrimitiveType.NODE) return NODE;
+        if (type == OsmPrimitiveType.WAY) return WAY;
+        if (type == OsmPrimitiveType.CLOSEDWAY) return CLOSEDWAY;
+        if (type == OsmPrimitiveType.RELATION || type == OsmPrimitiveType.MULTIPOLYGON)
+                return RELATION;
+        throw new IllegalArgumentException("Unexpected primitive type: " + type);
+    }
+
+    /**
+     * Determines the {@code TaggingPresetType} from a given string.
+     * @param type The OSM primitive type as string ("node", "way", "relation" or "closedway")
+     * @return the {@code TaggingPresetType} from {@code type}
+     */
+    public static TaggingPresetType fromString(String type) {
+        for (TaggingPresetType t : TaggingPresetType.values()) {
+            if (t.getName().equals(type)) {
+                return t;
+            }
+        }
+        return null;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(revision 8863)
@@ -0,0 +1,122 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+
+/**
+ * Class holding Tagging Presets and allowing to manage them.
+ * @since 7100
+ */
+public final class TaggingPresets {
+
+    /** The collection of tagging presets */
+    private static final Collection<TaggingPreset> taggingPresets = new ArrayList<>();
+
+    /** The collection of listeners */
+    private static final Collection<TaggingPresetListener> listeners = new ArrayList<>();
+
+    private TaggingPresets() {
+        // Hide constructor for utility classes
+    }
+
+    /**
+     * Initializes tagging presets from preferences.
+     */
+    public static void readFromPreferences() {
+        taggingPresets.clear();
+        taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false));
+    }
+
+    /**
+     * Initialize the tagging presets (load and may display error)
+     */
+    public static void initialize() {
+        readFromPreferences();
+        for (TaggingPreset tp: taggingPresets) {
+            if (!(tp instanceof TaggingPresetSeparator)) {
+                Main.toolbar.register(tp);
+            }
+        }
+        if (taggingPresets.isEmpty()) {
+            Main.main.menu.presetsMenu.setVisible(false);
+        } else {
+            AutoCompletionManager.cachePresets(taggingPresets);
+            Map<TaggingPresetMenu, JMenu> submenus = new HashMap<>();
+            for (final TaggingPreset p : taggingPresets) {
+                JMenu m = p.group != null ? submenus.get(p.group) : Main.main.menu.presetsMenu;
+                if (m == null && p.group != null) {
+                    Main.error("No tagging preset submenu for " + p.group);
+                } else if (m == null) {
+                    Main.error("No tagging preset menu. Tagging preset " + p + " won't be available there");
+                } else if (p instanceof TaggingPresetSeparator) {
+                    m.add(new JSeparator());
+                } else if (p instanceof TaggingPresetMenu) {
+                    JMenu submenu = new JMenu(p);
+                    submenu.setText(p.getLocaleName());
+                    ((TaggingPresetMenu) p).menu = submenu;
+                    submenus.put((TaggingPresetMenu) p, submenu);
+                    m.add(submenu);
+                } else {
+                    JMenuItem mi = new JMenuItem(p);
+                    mi.setText(p.getLocaleName());
+                    m.add(mi);
+                }
+            }
+        }
+        if (Main.pref.getBoolean("taggingpreset.sortmenu")) {
+            TaggingPresetMenu.sortMenu(Main.main.menu.presetsMenu);
+        }
+    }
+
+    /**
+     * Replies a new collection containing all tagging presets.
+     * @return a new collection containing all tagging presets. Empty if presets are not initialized (never null)
+     */
+    public static Collection<TaggingPreset> getTaggingPresets() {
+        return new ArrayList<>(taggingPresets);
+    }
+
+    /**
+     * Adds a list of tagging presets to the current list.
+     * @param presets The tagging presets to add
+     */
+    public static void addTaggingPresets(Collection<TaggingPreset> presets) {
+        if (presets != null) {
+            if (taggingPresets.addAll(presets)) {
+                for (TaggingPresetListener listener : listeners) {
+                    listener.taggingPresetsModified();
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a tagging preset listener.
+     * @param listener The listener to add
+     */
+    public static void addListener(TaggingPresetListener listener) {
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a tagging preset listener.
+     * @param listener The listener to remove
+     */
+    public static void removeListener(TaggingPresetListener listener) {
+        if (listener != null) {
+            listeners.remove(listener);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(revision 8863)
@@ -0,0 +1,122 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Checkbox type.
+ */
+public class Check extends KeyedItem {
+
+    /** The localized version of {@link #text}. */
+    public String locale_text;
+    /** the value to set when checked (default is "yes") */
+    public String value_on = OsmUtils.trueval;
+    /** the value to set when unchecked (default is "no") */
+    public String value_off = OsmUtils.falseval;
+    /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */
+    public boolean disable_off;
+    /** "on" or "off" or unset (default is unset) */
+    public String default_; // only used for tagless objects
+
+    private QuadStateCheckBox check;
+    private QuadStateCheckBox.State initialState;
+    private Boolean def;
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+
+        // find out if our key is already used in the selection.
+        final Usage usage = determineBooleanUsage(sel, key);
+        final String oneValue = usage.values.isEmpty() ? null : usage.values.last();
+        def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null;
+
+        if (locale_text == null) {
+            locale_text = getLocaleText(text, text_context, null);
+        }
+
+        if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
+            if (def != null && !PROP_FILL_DEFAULT.get()) {
+                // default is set and filling default values feature is disabled - check if all primitives are untagged
+                for (OsmPrimitive s : sel) {
+                    if (s.hasKeys()) {
+                        def = null;
+                    }
+                }
+            }
+
+            // all selected objects share the same value which is either true or false or unset,
+            // we can display a standard check box.
+            initialState = value_on.equals(oneValue) || Boolean.TRUE.equals(def)
+                    ? QuadStateCheckBox.State.SELECTED
+                    : value_off.equals(oneValue) || Boolean.FALSE.equals(def)
+                    ? QuadStateCheckBox.State.NOT_SELECTED
+                    : QuadStateCheckBox.State.UNSET;
+
+        } else {
+            def = null;
+            // the objects have different values, or one or more objects have something
+            // else than true/false. we display a quad-state check box
+            // in "partial" state.
+            initialState = QuadStateCheckBox.State.PARTIAL;
+        }
+
+        final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4);
+        if (QuadStateCheckBox.State.PARTIAL.equals(initialState))
+            allowedStates.add(QuadStateCheckBox.State.PARTIAL);
+        allowedStates.add(QuadStateCheckBox.State.SELECTED);
+        if (!disable_off || value_off.equals(oneValue))
+            allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED);
+        allowedStates.add(QuadStateCheckBox.State.UNSET);
+        check = new QuadStateCheckBox(locale_text, initialState,
+                allowedStates.toArray(new QuadStateCheckBox.State[allowedStates.size()]));
+
+        p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
+        return true;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+        // if the user hasn't changed anything, don't create a command.
+        if (check.getState() == initialState && def == null) return;
+
+        // otherwise change things according to the selected value.
+        changedTags.add(new Tag(key,
+                check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
+                    check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
+                        null));
+    }
+
+    @Override
+    public MatchType getDefaultMatch() {
+        return MatchType.NONE;
+    }
+
+    @Override
+    public Collection<String> getValues() {
+        return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);
+    }
+
+    @Override
+    public String toString() {
+        return "Check ["
+                + (locale_text != null ? "locale_text=" + locale_text + ", " : "")
+                + (value_on != null ? "value_on=" + value_on + ", " : "")
+                + (value_off != null ? "value_off=" + value_off + ", " : "")
+                + "default_=" + default_ + ", "
+                + (check != null ? "check=" + check + ", " : "")
+                + (initialState != null ? "initialState=" + initialState
+                        + ", " : "") + "def=" + def + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java	(revision 8863)
@@ -0,0 +1,68 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.awt.GridLayout;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * A group of {@link Check}s.
+ * @since 6114
+ */
+public class CheckGroup extends TaggingPresetItem {
+
+    /**
+     * Number of columns (positive integer)
+     */
+    public String columns;
+
+    /**
+     * List of checkboxes
+     */
+    public final List<Check> checks = new LinkedList<>();
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        Integer cols = Integer.valueOf(columns);
+        int rows = (int) Math.ceil(checks.size()/cols.doubleValue());
+        JPanel panel = new JPanel(new GridLayout(rows, cols));
+
+        for (Check check : checks) {
+            check.addToPanel(panel, sel, presetInitiallyMatches);
+        }
+
+        p.add(panel, GBC.eol());
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+        for (Check check : checks) {
+            check.addCommands(changedTags);
+        }
+    }
+
+    @Override
+    protected Boolean matches(Map<String, String> tags) {
+        for (Check check : checks) {
+            if (Boolean.TRUE.equals(check.matches(tags))) {
+                return Boolean.TRUE;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "CheckGroup [columns=" + columns + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(revision 8863)
@@ -0,0 +1,104 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPriority;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Combobox type.
+ */
+public class Combo extends ComboMultiSelect {
+
+    public boolean editable = true;
+    protected JosmComboBox<PresetListEntry> combo;
+    public String length;
+
+    /**
+     * Constructs a new {@code Combo}.
+     */
+    public Combo() {
+        delimiter = ",";
+    }
+
+    @Override
+    protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
+        if (!usage.unused()) {
+            for (String s : usage.values) {
+                if (!lhm.containsKey(s)) {
+                    lhm.put(s, new PresetListEntry(s));
+                }
+            }
+        }
+        if (def != null && !lhm.containsKey(def)) {
+            lhm.put(def, new PresetListEntry(def));
+        }
+        lhm.put("", new PresetListEntry(""));
+
+        combo = new JosmComboBox<>(lhm.values().toArray(new PresetListEntry[0]));
+        component = combo;
+        combo.setRenderer(getListCellRenderer());
+        combo.setEditable(editable);
+        combo.reinitialize(lhm.values());
+        AutoCompletingTextField tf = new AutoCompletingTextField();
+        initAutoCompletionField(tf, key);
+        if (Main.pref.getBoolean("taggingpreset.display-keys-as-hint", true)) {
+            tf.setHint(key);
+        }
+        if (length != null && !length.isEmpty()) {
+            tf.setMaxChars(Integer.valueOf(length));
+        }
+        AutoCompletionList acList = tf.getAutoCompletionList();
+        if (acList != null) {
+            acList.add(getDisplayValues(), AutoCompletionItemPriority.IS_IN_STANDARD);
+        }
+        combo.setEditor(tf);
+
+        if (usage.hasUniqueValue()) {
+            // all items have the same value (and there were no unset items)
+            originalValue = lhm.get(usage.getFirst());
+            combo.setSelectedItem(originalValue);
+        } else if (def != null && usage.unused()) {
+            // default is set and all items were unset
+            if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
+                // selected osm primitives are untagged or filling default feature is enabled
+                combo.setSelectedItem(lhm.get(def).getDisplayValue(true));
+            } else {
+                // selected osm primitives are tagged and filling default feature is disabled
+                combo.setSelectedItem("");
+            }
+            originalValue = lhm.get(DIFFERENT);
+        } else if (usage.unused()) {
+            // all items were unset (and so is default)
+            originalValue = lhm.get("");
+            if ("force".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
+                combo.setSelectedItem(lhm.get(LAST_VALUES.get(key)));
+            } else {
+                combo.setSelectedItem(originalValue);
+            }
+        } else {
+            originalValue = lhm.get(DIFFERENT);
+            combo.setSelectedItem(originalValue);
+        }
+        p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
+    }
+
+    @Override
+    protected Object getSelectedItem() {
+        return combo.getSelectedItem();
+
+    }
+
+    @Override
+    protected String getDisplayIfNull() {
+        if (combo.isEditable())
+            return combo.getEditor().getItem().toString();
+        else
+            return null;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(revision 8863)
@@ -0,0 +1,491 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;
+import org.openstreetmap.josm.tools.AlphanumComparator;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Abstract superclass for combo box and multi-select list types.
+ */
+public abstract class ComboMultiSelect extends KeyedItem {
+
+    private static final ListCellRenderer<PresetListEntry> RENDERER = new ListCellRenderer<PresetListEntry>() {
+
+        private final JLabel lbl = new JLabel();
+
+        @Override
+        public Component getListCellRendererComponent(JList<? extends PresetListEntry> list, PresetListEntry item, int index,
+                boolean isSelected, boolean cellHasFocus) {
+
+            // Only return cached size, item is not shown
+            if (!list.isShowing() && item.prefferedWidth != -1 && item.prefferedHeight != -1) {
+                if (index == -1) {
+                    lbl.setPreferredSize(new Dimension(item.prefferedWidth, 10));
+                } else {
+                    lbl.setPreferredSize(new Dimension(item.prefferedWidth, item.prefferedHeight));
+                }
+                return lbl;
+            }
+
+            lbl.setPreferredSize(null);
+
+            if (isSelected) {
+                lbl.setBackground(list.getSelectionBackground());
+                lbl.setForeground(list.getSelectionForeground());
+            } 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.prefferedWidth = lbl.getPreferredSize().width;
+            item.prefferedHeight = lbl.getPreferredSize().height;
+
+            // We do not want the editor to have the maximum height of all
+            // entries. Return a dummy with bogus height.
+            if (index == -1) {
+                lbl.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10));
+            }
+            return lbl;
+        }
+    };
+
+    /** The localized version of {@link #text}. */
+    public String locale_text;
+    public String values;
+    public String values_from;
+    /** The context used for translating {@link #values} */
+    public String values_context;
+    public String display_values;
+    /** The localized version of {@link #display_values}. */
+    public String locale_display_values;
+    public String short_descriptions;
+    /** The localized version of {@link #short_descriptions}. */
+    public String locale_short_descriptions;
+    public String default_;
+    public String delimiter = ";";
+    public String use_last_as_default = "false";
+    /** whether to use values for search via {@link TaggingPresetSelector} */
+    public String values_searchable = "false";
+
+    protected JComponent component;
+    protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<>();
+    private boolean initialized;
+    protected Usage usage;
+    protected Object originalValue;
+
+    /**
+     * Class that allows list values to be assigned and retrieved as a comma-delimited
+     * string (extracted from TaggingPreset)
+     */
+    protected static class ConcatenatingJList extends JList<PresetListEntry> {
+        private final String delimiter;
+
+        protected ConcatenatingJList(String del, PresetListEntry[] o) {
+            super(o);
+            delimiter = del;
+        }
+
+        public void setSelectedItem(Object o) {
+            if (o == null) {
+                clearSelection();
+            } else {
+                String s = o.toString();
+                Set<String> parts = new TreeSet<>(Arrays.asList(s.split(delimiter)));
+                ListModel<PresetListEntry> lm = getModel();
+                int[] intParts = new int[lm.getSize()];
+                int j = 0;
+                for (int i = 0; i < lm.getSize(); i++) {
+                    final String value = lm.getElementAt(i).value;
+                    if (parts.contains(value)) {
+                        intParts[j++] = i;
+                        parts.remove(value);
+                    }
+                }
+                setSelectedIndices(Arrays.copyOf(intParts, j));
+                // check if we have actually managed to represent the full
+                // value with our presets. if not, cop out; we will not offer
+                // a selection list that threatens to ruin the value.
+                setEnabled(parts.isEmpty());
+            }
+        }
+
+        public String getSelectedItem() {
+            ListModel<PresetListEntry> lm = getModel();
+            int[] si = getSelectedIndices();
+            StringBuilder builder = new StringBuilder();
+            for (int i = 0; i < si.length; i++) {
+                if (i > 0) {
+                    builder.append(delimiter);
+                }
+                builder.append(lm.getElementAt(si[i]).value);
+            }
+            return builder.toString();
+        }
+    }
+
+    public static class PresetListEntry implements Comparable<PresetListEntry> {
+        public String value;
+        /** The context used for translating {@link #value} */
+        public String value_context;
+        public String display_value;
+        public String short_description;
+        /** The location of icon file to display */
+        public String icon;
+        /** The size of displayed icon. If not set, default is size from icon file */
+        public String icon_size;
+        /** The localized version of {@link #display_value}. */
+        public String locale_display_value;
+        /** The localized version of {@link #short_description}. */
+        public String locale_short_description;
+        private final File zipIcons = TaggingPresetReader.getZipIcons();
+
+        // Cached size (currently only for Combo) to speed up preset dialog initialization
+        public int prefferedWidth = -1;
+        public int prefferedHeight = -1;
+
+        /**
+         * Constructs a new {@code PresetListEntry}, uninitialized.
+         */
+        public PresetListEntry() {
+        }
+
+        public PresetListEntry(String value) {
+            this.value = value;
+        }
+
+        public String getListDisplay() {
+            if (value.equals(DIFFERENT))
+                return "<b>"+DIFFERENT.replaceAll("<", "&lt;").replaceAll(">", "&gt;")+"</b>";
+
+            if (value.isEmpty())
+                return "&nbsp;";
+
+            final StringBuilder res = new StringBuilder("<b>");
+            res.append(getDisplayValue(true).replaceAll("<", "&lt;").replaceAll(">", "&gt;"))
+               .append("</b>");
+            if (getShortDescription(true) != null) {
+                // wrap in table to restrict the text width
+                res.append("<div style=\"width:300px; padding:0 0 5px 5px\">")
+                   .append(getShortDescription(true))
+                   .append("</div>");
+            }
+            return res.toString();
+        }
+
+        /**
+         * Returns the entry icon, if any.
+         * @return the entry icon, or {@code null}
+         */
+        public ImageIcon getIcon() {
+            return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size));
+        }
+
+        public String getDisplayValue(boolean translated) {
+            return translated
+                    ? Utils.firstNonNull(locale_display_value, tr(display_value), trc(value_context, value))
+                            : Utils.firstNonNull(display_value, value);
+        }
+
+        public String getShortDescription(boolean translated) {
+            return translated
+                    ? Utils.firstNonNull(locale_short_description, tr(short_description))
+                            : short_description;
+        }
+
+        // toString is mainly used to initialize the Editor
+        @Override
+        public String toString() {
+            if (value.equals(DIFFERENT))
+                return DIFFERENT;
+            return getDisplayValue(true).replaceAll("<.*>", ""); // remove additional markup, e.g. <br>
+        }
+
+        @Override
+        public int compareTo(PresetListEntry o) {
+            return AlphanumComparator.getInstance().compare(this.getDisplayValue(true), o.getDisplayValue(true));
+        }
+    }
+
+    /**
+     * allow escaped comma in comma separated list:
+     * "A\, B\, C,one\, two" --&gt; ["A, B, C", "one, two"]
+     * @param delimiter the delimiter, e.g. a comma. separates the entries and
+     *      must be escaped within one entry
+     * @param s the string
+     */
+    public static String[] splitEscaped(char delimiter, String s) {
+        if (s == null)
+            return new String[0];
+        List<String> result = new ArrayList<>();
+        boolean backslash = false;
+        StringBuilder item = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char ch = s.charAt(i);
+            if (backslash) {
+                item.append(ch);
+                backslash = false;
+            } else if (ch == '\\') {
+                backslash = true;
+            } else if (ch == delimiter) {
+                result.add(item.toString());
+                item.setLength(0);
+            } else {
+                item.append(ch);
+            }
+        }
+        if (item.length() > 0) {
+            result.add(item.toString());
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    protected abstract Object getSelectedItem();
+
+    protected abstract void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches);
+
+    protected char getDelChar() {
+        return delimiter.isEmpty() ? ';' : delimiter.charAt(0);
+    }
+
+    @Override
+    public Collection<String> getValues() {
+        initListEntries();
+        return lhm.keySet();
+    }
+
+    public Collection<String> getDisplayValues() {
+        initListEntries();
+        return Utils.transform(lhm.values(), new Utils.Function<PresetListEntry, String>() {
+            @Override
+            public String apply(PresetListEntry x) {
+                return x.getDisplayValue(true);
+            }
+        });
+    }
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+
+        initListEntries();
+
+        // find out if our key is already used in the selection.
+        usage = determineTextUsage(sel, key);
+        if (!usage.hasUniqueValue() && !usage.unused()) {
+            lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT));
+        }
+
+        p.add(new JLabel(tr("{0}:", locale_text)), GBC.std().insets(0, 0, 10, 0));
+        addToPanelAnchor(p, default_, presetInitiallyMatches);
+
+        return true;
+
+    }
+
+    private void initListEntries() {
+        if (initialized) {
+            lhm.remove(DIFFERENT); // possibly added in #addToPanel
+            return;
+        } else if (lhm.isEmpty()) {
+            initListEntriesFromAttributes();
+        } else {
+            if (values != null) {
+                Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
+                        + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
+                        key, text, "values", "list_entry"));
+            }
+            if (display_values != null || locale_display_values != null) {
+                Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
+                        + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
+                        key, text, "display_values", "list_entry"));
+            }
+            if (short_descriptions != null || locale_short_descriptions != null) {
+                Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
+                        + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
+                        key, text, "short_descriptions", "list_entry"));
+            }
+            for (PresetListEntry e : lhm.values()) {
+                if (e.value_context == null) {
+                    e.value_context = values_context;
+                }
+            }
+        }
+        if (locale_text == null) {
+            locale_text = getLocaleText(text, text_context, null);
+        }
+        initialized = true;
+    }
+
+    private void initListEntriesFromAttributes() {
+        char delChar = getDelChar();
+
+        String[] value_array = null;
+
+        if (values_from != null) {
+            String[] class_method = values_from.split("#");
+            if (class_method != null && class_method.length == 2) {
+                try {
+                    Method method = Class.forName(class_method[0]).getMethod(class_method[1]);
+                    // Check method is public static String[] methodName()
+                    int mod = method.getModifiers();
+                    if (Modifier.isPublic(mod) && Modifier.isStatic(mod)
+                            && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) {
+                        value_array = (String[]) method.invoke(null);
+                    } else {
+                        Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,
+                                "public static String[] methodName()"));
+                    }
+                } catch (Exception e) {
+                    Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text,
+                            e.getClass().getName(), e.getMessage()));
+                }
+            }
+        }
+
+        if (value_array == null) {
+            value_array = splitEscaped(delChar, values);
+        }
+
+        final String displ = Utils.firstNonNull(locale_display_values, display_values);
+        String[] display_array = displ == null ? value_array : splitEscaped(delChar, displ);
+
+        final String descr = Utils.firstNonNull(locale_short_descriptions, short_descriptions);
+        String[] short_descriptions_array = descr == null ? null : splitEscaped(delChar, descr);
+
+        if (display_array.length != value_array.length) {
+            Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''",
+                            key, text));
+            display_array = value_array;
+        }
+
+        if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) {
+            Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''",
+                            key, text));
+            short_descriptions_array = null;
+        }
+
+        final List<PresetListEntry> entries = new ArrayList<>(value_array.length);
+        for (int i = 0; i < value_array.length; i++) {
+            final PresetListEntry e = new PresetListEntry(value_array[i]);
+            e.locale_display_value = locale_display_values != null
+                    ? display_array[i]
+                    : trc(values_context, fixPresetString(display_array[i]));
+            if (short_descriptions_array != null) {
+                e.locale_short_description = locale_short_descriptions != null
+                        ? short_descriptions_array[i]
+                        : tr(fixPresetString(short_descriptions_array[i]));
+            }
+
+            entries.add(e);
+        }
+
+        if (Main.pref.getBoolean("taggingpreset.sortvalues", true)) {
+            Collections.sort(entries);
+        }
+
+        for (PresetListEntry i : entries) {
+            lhm.put(i.value, i);
+        }
+    }
+
+    protected String getDisplayIfNull() {
+        return null;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+        Object obj = getSelectedItem();
+        String display = (obj == null) ? null : obj.toString();
+        String value = null;
+        if (display == null) {
+            display = getDisplayIfNull();
+        }
+
+        if (display != null) {
+            for (Entry<String, PresetListEntry> entry : lhm.entrySet()) {
+                String k = entry.getValue().toString();
+                if (k != null && k.equals(display)) {
+                    value = entry.getKey();
+                    break;
+                }
+            }
+            if (value == null) {
+                value = display;
+            }
+        } else {
+            value = "";
+        }
+        value = Tag.removeWhiteSpaces(value);
+
+        // no change if same as before
+        if (originalValue == null) {
+            if (value.isEmpty())
+                return;
+        } else if (value.equals(originalValue.toString()))
+            return;
+
+        if (!"false".equals(use_last_as_default)) {
+            LAST_VALUES.put(key, value);
+        }
+        changedTags.add(new Tag(key, value));
+    }
+
+    public void addListEntry(PresetListEntry e) {
+        lhm.put(e.value, e);
+    }
+
+    public void addListEntries(Collection<PresetListEntry> e) {
+        for (PresetListEntry i : e) {
+            addListEntry(i);
+        }
+    }
+
+    protected ListCellRenderer<PresetListEntry> getListCellRenderer() {
+        return RENDERER;
+    }
+
+    @Override
+    public MatchType getDefaultMatch() {
+        return MatchType.NONE;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparator.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparator.java	(revision 8863)
@@ -0,0 +1,35 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Class used to represent a {@link JSeparator} inside tagging preset window.
+ * @since 6198
+ */
+public class ItemSeparator extends TaggingPresetItem {
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+    }
+
+    @Override
+    public String toString() {
+        return "ItemSeparator";
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java	(revision 8863)
@@ -0,0 +1,47 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+
+/**
+ * Invisible type allowing to hardcode an OSM key/value from the preset definition.
+ */
+public class Key extends KeyedItem {
+
+    /** The hardcoded value for key */
+    public String value;
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+        changedTags.add(new Tag(key, value));
+    }
+
+    @Override
+    public MatchType getDefaultMatch() {
+        return MatchType.KEY_VALUE_REQUIRED;
+    }
+
+    @Override
+    public Collection<String> getValues() {
+        return Collections.singleton(value);
+    }
+
+    @Override
+    public String toString() {
+        return "Key [key=" + key + ", value=" + value + ", text=" + text
+                + ", text_context=" + text_context + ", match=" + match
+                + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 8863)
@@ -0,0 +1,162 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+
+/**
+ * Preset item associated to an OSM key.
+ */
+public abstract class KeyedItem extends TaggingPresetItem {
+
+    /** Translatation of "&lt;different&gt;". Use in combo boxes to display an entry matching several different values. */
+    protected static final String DIFFERENT = tr("<different>");
+
+    protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
+
+    /** Last value of each key used in presets, used for prefilling corresponding fields */
+    protected static final Map<String, String> LAST_VALUES = new HashMap<>();
+
+    public String key;
+    /** The text to display */
+    public String text;
+    /** The context used for translating {@link #text} */
+    public String text_context;
+    public String match = getDefaultMatch().getValue();
+
+    /**
+     * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
+     */
+    protected enum MatchType {
+
+        /** Neutral, i.e., do not consider this item for matching. */
+        NONE("none"),
+        /** Positive if key matches, neutral otherwise. */
+        KEY("key"),
+        /** Positive if key matches, negative otherwise. */
+        KEY_REQUIRED("key!"),
+        /** Positive if key and value matches, neutral otherwise. */
+        KEY_VALUE("keyvalue"),
+        /** Positive if key and value matches, negative otherwise. */
+        KEY_VALUE_REQUIRED("keyvalue!");
+
+        private final String value;
+
+        MatchType(String value) {
+            this.value = value;
+        }
+
+        /**
+         * Replies the associated textual value.
+         * @return the associated textual value
+         */
+        public String getValue() {
+            return value;
+        }
+
+        /**
+         * Determines the {@code MatchType} for the given textual value.
+         * @param type the textual value
+         * @return the {@code MatchType} for the given textual value
+         */
+        public static MatchType ofString(String type) {
+            for (MatchType i : EnumSet.allOf(MatchType.class)) {
+                if (i.getValue().equals(type))
+                    return i;
+            }
+            throw new IllegalArgumentException(type + " is not allowed");
+        }
+    }
+
+    protected static class Usage {
+        public SortedSet<String> values;
+        private boolean hadKeys;
+        private boolean hadEmpty;
+
+        public boolean hasUniqueValue() {
+            return values.size() == 1 && !hadEmpty;
+        }
+
+        public boolean unused() {
+            return values.isEmpty();
+        }
+
+        public String getFirst() {
+            return values.first();
+        }
+
+        public boolean hadKeys() {
+            return hadKeys;
+        }
+    }
+
+    protected static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
+        Usage returnValue = new Usage();
+        returnValue.values = new TreeSet<>();
+        for (OsmPrimitive s : sel) {
+            String v = s.get(key);
+            if (v != null) {
+                returnValue.values.add(v);
+            } else {
+                returnValue.hadEmpty = true;
+            }
+            if (s.hasKeys()) {
+                returnValue.hadKeys = true;
+            }
+        }
+        return returnValue;
+    }
+
+    protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
+
+        Usage returnValue = new Usage();
+        returnValue.values = new TreeSet<>();
+        for (OsmPrimitive s : sel) {
+            String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
+            if (booleanValue != null) {
+                returnValue.values.add(booleanValue);
+            }
+        }
+        return returnValue;
+    }
+
+    public abstract MatchType getDefaultMatch();
+
+    public abstract Collection<String> getValues();
+
+    @Override
+    protected Boolean matches(Map<String, String> tags) {
+        switch (MatchType.ofString(match)) {
+        case NONE:
+            return null;
+        case KEY:
+            return tags.containsKey(key) ? Boolean.TRUE : null;
+        case KEY_REQUIRED:
+            return tags.containsKey(key);
+        case KEY_VALUE:
+            return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null;
+        case KEY_VALUE_REQUIRED:
+            return tags.containsKey(key) && getValues().contains(tags.get(key));
+        default:
+            throw new IllegalStateException();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "KeyedItem [key=" + key + ", text=" + text
+                + ", text_context=" + text_context + ", match=" + match
+                + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java	(revision 8863)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.Collection;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Label type.
+ */
+public class Label extends TextItem {
+
+    /** The location of icon file to display (optional) */
+    public String icon;
+    /** The size of displayed icon. If not set, default is 16px */
+    public String icon_size;
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        initializeLocaleText(null);
+        addLabel(p, getIcon(), locale_text);
+        return true;
+    }
+
+    /**
+     * Adds a new {@code JLabel} to the given panel.
+     * @param p The panel
+     * @param icon the icon (optional, can be null)
+     * @param label The text label
+     */
+    public static void addLabel(JPanel p, Icon icon, String label) {
+        p.add(new JLabel(label, icon, JLabel.LEADING), GBC.eol().fill(GBC.HORIZONTAL));
+    }
+
+    /**
+     * Returns the label icon, if any.
+     * @return the label icon, or {@code null}
+     */
+    public ImageIcon getIcon() {
+        Integer size = parseInteger(icon_size);
+        return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), size != null ? size : 16);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java	(revision 8863)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.widgets.UrlLabel;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Hyperlink type.
+ */
+public class Link extends TextItem {
+
+    /** The link to display. */
+    public String href;
+
+    /** The localized version of {@link #href}. */
+    public String locale_href;
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        initializeLocaleText(tr("More information about this feature"));
+        String url = locale_href;
+        if (url == null) {
+            url = href;
+        }
+        if (url != null) {
+            p.add(new UrlLabel(url, locale_text, 2), GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL));
+        }
+        return false;
+    }
+
+    @Override
+    protected String fieldsToString() {
+        return super.fieldsToString()
+                + (href != null ? "href=" + href + ", " : "")
+                + (locale_href != null ? "locale_href=" + locale_href + ", " : "");
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(revision 8863)
@@ -0,0 +1,69 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.awt.Dimension;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListCellRenderer;
+
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Multi-select list type.
+ */
+public class MultiSelect extends ComboMultiSelect {
+
+    /**
+     * Number of rows to display (positive integer, optional).
+     */
+    public String rows;
+    protected ConcatenatingJList list;
+
+    @Override
+    protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
+        list = new ConcatenatingJList(delimiter, lhm.values().toArray(new PresetListEntry[0]));
+        component = list;
+        ListCellRenderer<PresetListEntry> renderer = getListCellRenderer();
+        list.setCellRenderer(renderer);
+
+        if (usage.hasUniqueValue() && !usage.unused()) {
+            originalValue = usage.getFirst();
+            list.setSelectedItem(originalValue);
+        } else if (def != null && !usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
+            originalValue = DIFFERENT;
+            list.setSelectedItem(def);
+        } else if (usage.unused()) {
+            originalValue = null;
+            list.setSelectedItem(originalValue);
+        } else {
+            originalValue = DIFFERENT;
+            list.setSelectedItem(originalValue);
+        }
+
+        JScrollPane sp = new JScrollPane(list);
+        // if a number of rows has been specified in the preset,
+        // modify preferred height of scroll pane to match that row count.
+        if (rows != null) {
+            double height = renderer.getListCellRendererComponent(list,
+                    new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * Integer.parseInt(rows);
+            sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height));
+        }
+        p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));
+    }
+
+    @Override
+    protected Object getSelectedItem() {
+        return list.getSelectedItem();
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+        // Do not create any commands if list has been disabled because of an unknown value (fix #8605)
+        if (list.isEnabled()) {
+            super.addCommands(changedTags);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java	(revision 8863)
@@ -0,0 +1,25 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.tools.GBC;
+
+public class Optional extends TextItem {
+
+    // TODO: Draw a box around optional stuff
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        initializeLocaleText(tr("Optional Attributes:"));
+        p.add(new JLabel(" "), GBC.eol()); // space
+        p.add(new JLabel(locale_text), 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 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java	(revision 8863)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Predicate;
+import org.openstreetmap.josm.tools.Utils;
+
+public class PresetLink extends TaggingPresetItem {
+
+    public String preset_name = "";
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        final String presetName = preset_name;
+        final TaggingPreset t = Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() {
+            @Override
+            public boolean evaluate(TaggingPreset object) {
+                return presetName.equals(object.name);
+            }
+        }).iterator().next();
+        if (t == null) return false;
+        JLabel lbl = new TaggingPresetLabel(t);
+        lbl.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                t.actionPerformed(null);
+            }
+        });
+        p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 8863)
@@ -0,0 +1,132 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.actions.search.SearchAction;
+import org.openstreetmap.josm.actions.search.SearchCompiler;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.xml.sax.SAXException;
+
+public class Roles extends TaggingPresetItem {
+
+    public static class Role {
+        public Set<TaggingPresetType> types;
+        public String key;
+        /** The text to display */
+        public String text;
+        /** The context used for translating {@link #text} */
+        public String text_context;
+        /** The localized version of {@link #text}. */
+        public String locale_text;
+        public SearchCompiler.Match memberExpression;
+
+        public boolean required;
+        private long count;
+
+        public void setType(String types) throws SAXException {
+            this.types = getType(types);
+        }
+
+        public void setRequisite(String str) throws SAXException {
+            if ("required".equals(str)) {
+                required = true;
+            } else if (!"optional".equals(str))
+                throw new SAXException(tr("Unknown requisite: {0}", str));
+        }
+
+        public void setMember_expression(String member_expression) throws SAXException {
+            try {
+                final SearchAction.SearchSetting searchSetting = new SearchAction.SearchSetting();
+                searchSetting.text = member_expression;
+                searchSetting.caseSensitive = true;
+                searchSetting.regexSearch = true;
+                this.memberExpression = SearchCompiler.compile(searchSetting);
+            } catch (SearchCompiler.ParseError ex) {
+                throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
+            }
+        }
+
+        public void setCount(String count) {
+            this.count = Long.parseLong(count);
+        }
+
+        /**
+         * Return either argument, the highest possible value or the lowest allowed value
+         */
+        public long getValidCount(long c) {
+            if (count > 0 && !required)
+                return c != 0 ? count : 0;
+            else if (count > 0)
+                return count;
+            else if (!required)
+                return c != 0 ? c : 0;
+            else
+                return c != 0 ? c : 1;
+        }
+
+        public boolean addToPanel(JPanel p) {
+            String cstring;
+            if (count > 0 && !required) {
+                cstring = "0,"+count;
+            } else if (count > 0) {
+                cstring = String.valueOf(count);
+            } else if (!required) {
+                cstring = "0-...";
+            } else {
+                cstring = "1-...";
+            }
+            if (locale_text == null) {
+                locale_text = getLocaleText(text, text_context, null);
+            }
+            p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));
+            p.add(new JLabel(key), GBC.std().insets(0, 0, 10, 0));
+            p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0, 0, 10, 0));
+            if (types != null) {
+                JPanel pp = new JPanel();
+                for (TaggingPresetType t : types) {
+                    pp.add(new JLabel(ImageProvider.get(t.getIconName())));
+                }
+                p.add(pp, GBC.eol());
+            }
+            return true;
+        }
+    }
+
+    public final List<Role> roles = new LinkedList<>();
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        p.add(new JLabel(" "), GBC.eol()); // space
+        if (!roles.isEmpty()) {
+            JPanel proles = new JPanel(new GridBagLayout());
+            proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));
+            proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));
+            proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));
+            proles.add(new JLabel(tr("elements")), GBC.eol());
+            for (Role i : roles) {
+                i.addToPanel(proles);
+            }
+            p.add(proles, GBC.eol());
+        }
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Space.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Space.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Space.java	(revision 8863)
@@ -0,0 +1,34 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Horizontal separator type.
+ */
+public class Space extends TaggingPresetItem {
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+        p.add(new JLabel(" "), GBC.eol()); // space
+        return false;
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+    }
+
+    @Override
+    public String toString() {
+        return "Space";
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(revision 8863)
@@ -0,0 +1,228 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Text field type.
+ */
+public class Text extends KeyedItem {
+
+    private static int auto_increment_selected;
+
+    /** The localized version of {@link #text}. */
+    public String locale_text;
+    public String default_;
+    public String originalValue;
+    public String use_last_as_default = "false";
+    public String auto_increment;
+    public String length;
+    public String alternative_autocomplete_keys;
+
+    private JComponent value;
+
+    @Override
+    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
+
+        // find out if our key is already used in the selection.
+        Usage usage = determineTextUsage(sel, key);
+        AutoCompletingTextField textField = new AutoCompletingTextField();
+        if (alternative_autocomplete_keys != null) {
+            initAutoCompletionField(textField, (key + ',' + alternative_autocomplete_keys).split(","));
+        } else {
+            initAutoCompletionField(textField, key);
+        }
+        if (Main.pref.getBoolean("taggingpreset.display-keys-as-hint", true)) {
+            textField.setHint(key);
+        }
+        if (length != null && !length.isEmpty()) {
+            textField.setMaxChars(Integer.valueOf(length));
+        }
+        if (usage.unused()) {
+            if (auto_increment_selected != 0  && auto_increment != null) {
+                try {
+                    textField.setText(Integer.toString(Integer.parseInt(
+                            LAST_VALUES.get(key)) + auto_increment_selected));
+                } catch (NumberFormatException ex) {
+                    // Ignore - cannot auto-increment if last was non-numeric
+                    if (Main.isTraceEnabled()) {
+                        Main.trace(ex.getMessage());
+                    }
+                }
+            } else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
+                // selected osm primitives are untagged or filling default values feature is enabled
+                if (!"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
+                    textField.setText(LAST_VALUES.get(key));
+                } else {
+                    textField.setText(default_);
+                }
+            } else {
+                // selected osm primitives are tagged and filling default values feature is disabled
+                textField.setText("");
+            }
+            value = textField;
+            originalValue = null;
+        } else if (usage.hasUniqueValue()) {
+            // all objects use the same value
+            textField.setText(usage.getFirst());
+            value = textField;
+            originalValue = usage.getFirst();
+        } else {
+            // the objects have different values
+            JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0]));
+            comboBox.setEditable(true);
+            comboBox.setEditor(textField);
+            comboBox.getEditor().setItem(DIFFERENT);
+            value = comboBox;
+            originalValue = DIFFERENT;
+        }
+        if (locale_text == null) {
+            locale_text = getLocaleText(text, text_context, null);
+        }
+
+        // if there's an auto_increment setting, then wrap the text field
+        // into a panel, appending a number of buttons.
+        // auto_increment has a format like -2,-1,1,2
+        // the text box being the first component in the panel is relied
+        // on in a rather ugly fashion further down.
+        if (auto_increment != null) {
+            ButtonGroup bg = new ButtonGroup();
+            JPanel pnl = new JPanel(new GridBagLayout());
+            pnl.add(value, GBC.std().fill(GBC.HORIZONTAL));
+
+            // first, one button for each auto_increment value
+            for (final String ai : auto_increment.split(",")) {
+                JToggleButton aibutton = new JToggleButton(ai);
+                aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai));
+                aibutton.setMargin(new java.awt.Insets(0, 0, 0, 0));
+                aibutton.setFocusable(false);
+                saveHorizontalSpace(aibutton);
+                bg.add(aibutton);
+                try {
+                    // TODO there must be a better way to parse a number like "+3" than this.
+                    final int buttonvalue = (NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue();
+                    if (auto_increment_selected == buttonvalue) aibutton.setSelected(true);
+                    aibutton.addActionListener(new ActionListener() {
+                        @Override
+                        public void actionPerformed(ActionEvent e) {
+                            auto_increment_selected = buttonvalue;
+                        }
+                    });
+                    pnl.add(aibutton, GBC.std());
+                } catch (ParseException x) {
+                    Main.error("Cannot parse auto-increment value of '" + ai + "' into an integer");
+                }
+            }
+
+            // an invisible toggle button for "release" of the button group
+            final JToggleButton clearbutton = new JToggleButton("X");
+            clearbutton.setVisible(false);
+            clearbutton.setFocusable(false);
+            bg.add(clearbutton);
+            // and its visible counterpart. - this mechanism allows us to
+            // have *no* button selected after the X is clicked, instead
+            // of the X remaining selected
+            JButton releasebutton = new JButton("X");
+            releasebutton.setToolTipText(tr("Cancel auto-increment for this field"));
+            releasebutton.setMargin(new java.awt.Insets(0, 0, 0, 0));
+            releasebutton.setFocusable(false);
+            releasebutton.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    auto_increment_selected = 0;
+                    clearbutton.setSelected(true);
+                }
+            });
+            saveHorizontalSpace(releasebutton);
+            pnl.add(releasebutton, GBC.eol());
+            value = pnl;
+        }
+        p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));
+        p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
+        return true;
+    }
+
+    private static void saveHorizontalSpace(AbstractButton button) {
+        Insets insets = button.getBorder().getBorderInsets(button);
+        // Ensure the current look&feel does not waste horizontal space (as seen in Nimbus & Aqua)
+        if (insets != null && insets.left+insets.right > insets.top+insets.bottom) {
+            int min = Math.min(insets.top, insets.bottom);
+            button.setBorder(BorderFactory.createEmptyBorder(insets.top, min, insets.bottom, min));
+        }
+    }
+
+    private static String getValue(Component comp) {
+        if (comp instanceof JosmComboBox) {
+            return ((JosmComboBox<?>) comp).getEditor().getItem().toString();
+        } else if (comp instanceof JosmTextField) {
+            return ((JosmTextField) comp).getText();
+        } else if (comp instanceof JPanel) {
+            return getValue(((JPanel) comp).getComponent(0));
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+
+        // return if unchanged
+        String v = getValue(value);
+        if (v == null) {
+            Main.error("No 'last value' support for component " + value);
+            return;
+        }
+
+        v = Tag.removeWhiteSpaces(v);
+
+        if (!"false".equals(use_last_as_default) || auto_increment != null) {
+            LAST_VALUES.put(key, v);
+        }
+        if (v.equals(originalValue) || (originalValue == null && v.isEmpty()))
+            return;
+
+        changedTags.add(new Tag(key, v));
+        AutoCompletionManager.rememberUserInput(key, v, true);
+    }
+
+    @Override
+    public MatchType getDefaultMatch() {
+        return MatchType.NONE;
+    }
+
+    @Override
+    public Collection<String> getValues() {
+        if (default_ == null || default_.isEmpty())
+            return Collections.emptyList();
+        return Collections.singleton(default_);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/TextItem.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/TextItem.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/TextItem.java	(revision 8863)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+
+/**
+ * A tagging preset item displaying a localizable text.
+ * @since 6190
+ */
+public abstract class TextItem extends TaggingPresetItem {
+
+    /** The text to display */
+    public String text;
+
+    /** The context used for translating {@link #text} */
+    public String text_context;
+
+    /** The localized version of {@link #text} */
+    public String locale_text;
+
+    protected final void initializeLocaleText(String defaultText) {
+        if (locale_text == null) {
+            locale_text = getLocaleText(text, text_context, defaultText);
+        }
+    }
+
+    @Override
+    public void addCommands(List<Tag> changedTags) {
+    }
+
+    protected String fieldsToString() {
+        return (text != null ? "text=" + text + ", " : "")
+                + (text_context != null ? "text_context=" + text_context + ", " : "")
+                + (locale_text != null ? "locale_text=" + locale_text : "");
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" + fieldsToString() + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/package-info.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/package-info.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/package-info.java	(revision 8863)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides classes for handling tagging presets items.
+ */
+package org.openstreetmap.josm.gui.tagging.presets.items;
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/presets/package-info.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/presets/package-info.java	(revision 8863)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/presets/package-info.java	(revision 8863)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides classes for handling tagging presets.
+ */
+package org.openstreetmap.josm.gui.tagging.presets;
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/ConditionalKeysTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/ConditionalKeysTest.groovy	(revision 8862)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/ConditionalKeysTest.groovy	(revision 8863)
@@ -3,5 +3,5 @@
 
 import org.openstreetmap.josm.JOSMFixture
-import org.openstreetmap.josm.gui.tagging.TaggingPresets
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 
 class ConditionalKeysTest extends GroovyTestCase {
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java	(revision 8862)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java	(revision 8863)
@@ -21,8 +21,8 @@
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.validation.Severity;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetItems;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetReader;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
 
 /**
@@ -213,8 +213,8 @@
         for (final TaggingPreset p : presets) {
             for (final TaggingPresetItem i : p.data) {
-                if (i instanceof TaggingPresetItems.KeyedItem &&
-                        Arrays.asList("opening_hours", "service_times", "collection_times").contains(((TaggingPresetItems.KeyedItem) i).key)) {
-                    for (final String v : ((TaggingPresetItems.KeyedItem) i).getValues()) {
-                        values.add(new Tag(((TaggingPresetItems.KeyedItem) i).key, v));
+                if (i instanceof KeyedItem &&
+                        Arrays.asList("opening_hours", "service_times", "collection_times").contains(((KeyedItem) i).key)) {
+                    for (final String v : ((KeyedItem) i).getValues()) {
+                        values.add(new Tag(((KeyedItem) i).key, v));
                     }
                 }
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/RelationCheckerTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/RelationCheckerTest.groovy	(revision 8862)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/RelationCheckerTest.groovy	(revision 8863)
@@ -9,5 +9,5 @@
 import org.openstreetmap.josm.data.osm.Way
 import org.openstreetmap.josm.data.validation.TestError
-import org.openstreetmap.josm.gui.tagging.TaggingPresets
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 
 class RelationCheckerTest extends GroovyTestCase {
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java	(revision 8862)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java	(revision 8863)
@@ -13,5 +13,5 @@
 import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.gui.tagging.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 
 /**
Index: /trunk/test/unit/org/openstreetmap/josm/gui/DefaultNameFormatterTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/DefaultNameFormatterTest.java	(revision 8862)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/DefaultNameFormatterTest.java	(revision 8863)
@@ -20,6 +20,6 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.tagging.TaggingPresetReader;
-import org.openstreetmap.josm.gui.tagging.TaggingPresets;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.io.Compression;
 import org.openstreetmap.josm.io.IllegalDataException;
Index: unk/test/unit/org/openstreetmap/josm/gui/tagging/PresetClassificationsTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/tagging/PresetClassificationsTest.java	(revision 8862)
+++ 	(revision )
@@ -1,72 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.openstreetmap.josm.JOSMFixture;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmUtils;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.tools.Utils;
-import org.xml.sax.SAXException;
-
-public class PresetClassificationsTest {
-
-    static final TaggingPresetSelector.PresetClassifications classifications = new TaggingPresetSelector.PresetClassifications();
-
-    /**
-     * Setup test.
-     */
-    @BeforeClass
-    public static void setUp() throws IOException, SAXException {
-        JOSMFixture.createUnitTestFixture().init();
-        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll("resource://data/defaultpresets.xml", true);
-        classifications.loadPresets(presets);
-    }
-
-    private List<TaggingPresetSelector.PresetClassification> getMatchingPresets(String searchText, OsmPrimitive w) {
-        return classifications.getMatchingPresets(searchText, true, true, EnumSet.of(TaggingPresetType.forPrimitive(w)),
-                Collections.singleton(w));
-    }
-
-    private List<String> getMatchingPresetNames(String searchText, OsmPrimitive w) {
-        return Utils.transform(getMatchingPresets(searchText, w), new Utils.Function<TaggingPresetSelector.PresetClassification, String>() {
-            @Override
-            public String apply(TaggingPresetSelector.PresetClassification x) {
-                return x.preset.name;
-            }
-        });
-    }
-
-    @Test
-    public void testBuilding() throws Exception {
-        final Way w = new Way();
-        final Node n1 = new Node();
-        w.addNode(n1);
-        w.addNode(new Node());
-        w.addNode(new Node());
-        assertFalse("unclosed way should not match building preset", getMatchingPresetNames("building", w).contains("Building"));
-        w.addNode(n1);
-        assertTrue("closed way should match building preset", getMatchingPresetNames("building", w).contains("Building"));
-    }
-
-    @Test
-    public void testRelationsForTram() {
-        final OsmPrimitive tram = OsmUtils.createPrimitive("way railway=tram");
-        assertTrue("railway=tram should match 'Railway Route' for relation creation", getMatchingPresetNames("route", tram)
-                .contains("Railway Route"));
-        assertTrue("railway=tram should match 'Public Transport Route' for relation creation", getMatchingPresetNames("route", tram)
-                .contains("Public Transport Route"));
-        assertFalse("railway=tram should not match 'Bus route'", getMatchingPresetNames("route", tram).contains("Bus route"));
-    }
-}
Index: unk/test/unit/org/openstreetmap/josm/gui/tagging/TaggingPresetReaderTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/tagging/TaggingPresetReaderTest.java	(revision 8862)
+++ 	(revision )
@@ -1,71 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.tagging;
-
-import static org.CustomMatchers.hasSize;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.openstreetmap.josm.JOSMFixture;
-import org.openstreetmap.josm.TestUtils;
-import org.openstreetmap.josm.tools.Utils;
-import org.xml.sax.SAXException;
-
-/**
- * Unit tests of {@link TaggingPresetReader} class.
- */
-public class TaggingPresetReaderTest {
-
-    /**
-     * Setup test.
-     */
-    @BeforeClass
-    public static void setUp() {
-        JOSMFixture.createUnitTestFixture().init();
-    }
-
-    /**
-     * #8954 - last checkbox in the preset is not added
-     */
-    @Test
-    public void test8954() throws SAXException, IOException {
-        String presetfile = TestUtils.getRegressionDataFile(8954, "preset.xml");
-        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, false);
-        Assert.assertEquals("Number of preset items", 1, presets.size());
-        final TaggingPreset preset = presets.iterator().next();
-        Assert.assertEquals("Number of entries", 1, preset.data.size());
-        final TaggingPresetItem item = preset.data.get(0);
-        Assert.assertTrue("Entry is not checkbox", item instanceof TaggingPresetItems.Check);
-    }
-
-    @Test
-    public void testNestedChunks() throws Exception {
-        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(TestUtils.getTestDataRoot() + "preset_chunk.xml", true);
-        assertThat(presets, hasSize(1));
-        final TaggingPreset abc =  presets.iterator().next();
-        final List<String> keys = Utils.transform(abc.data, new Utils.Function<TaggingPresetItem, String>() {
-            @Override
-            public String apply(TaggingPresetItem x) {
-                return ((TaggingPresetItems.Key) x).key;
-            }
-        });
-        assertEquals("[A1, A2, A3, B1, B2, B3, C1, C2, C3]", keys.toString());
-    }
-
-    /**
-     * Validate internal presets
-     * See #9027
-     */
-    @Test
-    public void readDefaulPresets() throws SAXException, IOException {
-        String presetfile = "resource://data/defaultpresets.xml";
-        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true);
-        Assert.assertTrue("Default presets are empty", presets.size() > 0);
-    }
-}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java	(revision 8863)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java	(revision 8863)
@@ -0,0 +1,85 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassification;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassifications;
+import org.openstreetmap.josm.tools.Utils;
+import org.xml.sax.SAXException;
+
+/**
+ * Unit tests of {@link PresetClassifications} class.
+ */
+public class PresetClassificationsTest {
+
+    static final PresetClassifications classifications = new PresetClassifications();
+
+    /**
+     * Setup test.
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    @BeforeClass
+    public static void setUp() throws IOException, SAXException {
+        JOSMFixture.createUnitTestFixture().init();
+        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll("resource://data/defaultpresets.xml", true);
+        classifications.loadPresets(presets);
+    }
+
+    private List<PresetClassification> getMatchingPresets(String searchText, OsmPrimitive w) {
+        return classifications.getMatchingPresets(searchText, true, true, EnumSet.of(TaggingPresetType.forPrimitive(w)),
+                Collections.singleton(w));
+    }
+
+    private List<String> getMatchingPresetNames(String searchText, OsmPrimitive w) {
+        return Utils.transform(getMatchingPresets(searchText, w), new Utils.Function<PresetClassification, String>() {
+            @Override
+            public String apply(PresetClassification x) {
+                return x.preset.name;
+            }
+        });
+    }
+
+    /**
+     * Test building preset.
+     */
+    @Test
+    public void testBuilding() {
+        final Way w = new Way();
+        final Node n1 = new Node();
+        w.addNode(n1);
+        w.addNode(new Node());
+        w.addNode(new Node());
+        assertFalse("unclosed way should not match building preset", getMatchingPresetNames("building", w).contains("Building"));
+        w.addNode(n1);
+        assertTrue("closed way should match building preset", getMatchingPresetNames("building", w).contains("Building"));
+    }
+
+    /**
+     * Test public transport tram relations presets.
+     */
+    @Test
+    public void testRelationsForTram() {
+        final OsmPrimitive tram = OsmUtils.createPrimitive("way railway=tram");
+        assertTrue("railway=tram should match 'Railway Route' for relation creation", getMatchingPresetNames("route", tram)
+                .contains("Railway Route"));
+        assertTrue("railway=tram should match 'Public Transport Route' for relation creation", getMatchingPresetNames("route", tram)
+                .contains("Public Transport Route"));
+        assertFalse("railway=tram should not match 'Bus route'", getMatchingPresetNames("route", tram).contains("Bus route"));
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java	(revision 8863)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java	(revision 8863)
@@ -0,0 +1,82 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.CustomMatchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.gui.tagging.presets.items.Check;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.tools.Utils;
+import org.xml.sax.SAXException;
+
+/**
+ * Unit tests of {@link TaggingPresetReader} class.
+ */
+public class TaggingPresetReaderTest {
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUp() {
+        JOSMFixture.createUnitTestFixture().init();
+    }
+
+    /**
+     * #8954 - last checkbox in the preset is not added
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    @Test
+    public void test8954() throws SAXException, IOException {
+        String presetfile = TestUtils.getRegressionDataFile(8954, "preset.xml");
+        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, false);
+        Assert.assertEquals("Number of preset items", 1, presets.size());
+        final TaggingPreset preset = presets.iterator().next();
+        Assert.assertEquals("Number of entries", 1, preset.data.size());
+        final TaggingPresetItem item = preset.data.get(0);
+        Assert.assertTrue("Entry is not checkbox", item instanceof Check);
+    }
+
+    /**
+     * Test nested chunks
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    @Test
+    public void testNestedChunks() throws SAXException, IOException {
+        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(TestUtils.getTestDataRoot() + "preset_chunk.xml", true);
+        assertThat(presets, hasSize(1));
+        final TaggingPreset abc =  presets.iterator().next();
+        final List<String> keys = Utils.transform(abc.data, new Utils.Function<TaggingPresetItem, String>() {
+            @Override
+            public String apply(TaggingPresetItem x) {
+                return ((Key) x).key;
+            }
+        });
+        assertEquals("[A1, A2, A3, B1, B2, B3, C1, C2, C3]", keys.toString());
+    }
+
+    /**
+     * Validate internal presets
+     * See #9027
+     * @throws SAXException if any XML error occurs
+     * @throws IOException if any I/O error occurs
+     */
+    @Test
+    public void readDefaulPresets() throws SAXException, IOException {
+        String presetfile = "resource://data/defaultpresets.xml";
+        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true);
+        Assert.assertTrue("Default presets are empty", presets.size() > 0);
+    }
+}
