Index: trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 6068)
@@ -25,5 +25,9 @@
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 
 /**
@@ -65,6 +69,6 @@
         if (presets != null) {
             for (TaggingPreset p : presets) {
-                for (TaggingPreset.Item i : p.data) {
-                    if (i instanceof TaggingPreset.Roles) {
+                for (TaggingPresetItem i : p.data) {
+                    if (i instanceof Roles) {
                         relationpresets.add(p);
                         break;
@@ -86,17 +90,17 @@
     @Override
     public void visit(Relation n) {
-        LinkedList<TaggingPreset.Role> allroles = new LinkedList<TaggingPreset.Role>();
+        LinkedList<Role> allroles = new LinkedList<Role>();
         for (TaggingPreset p : relationpresets) {
             boolean matches = true;
-            TaggingPreset.Roles r = null;
-            for (TaggingPreset.Item i : p.data) {
-                if (i instanceof TaggingPreset.Key) {
-                    TaggingPreset.Key k = (TaggingPreset.Key) i;
+            Roles r = null;
+            for (TaggingPresetItem i : p.data) {
+                if (i instanceof Key) {
+                    Key k = (Key) i;
                     if (!k.value.equals(n.get(k.key))) {
                         matches = false;
                         break;
                     }
-                } else if (i instanceof TaggingPreset.Roles) {
-                    r = (TaggingPreset.Roles) i;
+                } else if (i instanceof Roles) {
+                    r = (Roles) i;
                 }
             }
@@ -140,5 +144,5 @@
             } else {
                 LinkedList<String> done = new LinkedList<String>();
-                for (TaggingPreset.Role r : allroles) {
+                for (Role r : allroles) {
                     done.add(r.key);
                     String keyname = r.key;
@@ -168,11 +172,11 @@
                         Set<OsmPrimitive> wrongTypes = new HashSet<OsmPrimitive>();
                         if (r.types != null) {
-                            if (!r.types.contains(PresetType.WAY)) {
-                                wrongTypes.addAll(r.types.contains(PresetType.CLOSEDWAY) ? ri.openways : ri.ways);
-                            }
-                            if (!r.types.contains(PresetType.NODE)) {
+                            if (!r.types.contains(TaggingPresetType.WAY)) {
+                                wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
+                            }
+                            if (!r.types.contains(TaggingPresetType.NODE)) {
                                 wrongTypes.addAll(ri.nodes);
                             }
-                            if (!r.types.contains(PresetType.RELATION)) {
+                            if (!r.types.contains(TaggingPresetType.RELATION)) {
                                 wrongTypes.addAll(ri.relations);
                             }
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 6068)
@@ -57,4 +57,6 @@
 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.KeyedItem;
 import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.tools.GBC;
@@ -301,7 +303,7 @@
             }
             for (TaggingPreset p : presets) {
-                for (TaggingPreset.Item i : p.data) {
-                    if (i instanceof TaggingPreset.KeyedItem) {
-                        TaggingPreset.KeyedItem ky = (TaggingPreset.KeyedItem) i;
+                for (TaggingPresetItem i : p.data) {
+                    if (i instanceof KeyedItem) {
+                        KeyedItem ky = (KeyedItem) i;
                         if (ky.key != null && ky.getValues() != null) {
                             try {
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java	(revision 6068)
@@ -22,5 +22,5 @@
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 import org.openstreetmap.josm.tools.GBC;
 
@@ -77,5 +77,5 @@
     }
 
-    public void updatePresets(final Collection<PresetType> types, final Map<String, String> tags, PresetHandler presetHandler) {
+    public void updatePresets(final Collection<TaggingPresetType> types, final Map<String, String> tags, PresetHandler presetHandler) {
 
         removeAll();
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 6068)
@@ -84,5 +84,5 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
@@ -597,7 +597,7 @@
         final Map<String, String> tags = new HashMap<String, String>();
         valueCount.clear();
-        EnumSet<PresetType> types = EnumSet.noneOf(TaggingPreset.PresetType.class);
+        EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
         for (OsmPrimitive osm : newSelection) {
-            types.add(PresetType.forPrimitive(osm));
+            types.add(TaggingPresetType.forPrimitive(osm));
             for (String key : osm.keySet()) {
                 String value = osm.get(key);
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 6068)
@@ -85,4 +85,5 @@
 import org.openstreetmap.josm.gui.tagging.TagModel;
 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;
@@ -769,5 +770,5 @@
     public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) {
         try {
-            final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPreset.PresetType.RELATION), orig.getKeys(), false);
+            final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPresetType.RELATION), orig.getKeys(), false);
             Relation relation = new Relation(orig);
             boolean modified = false;
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 6068)
@@ -40,4 +40,5 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
 
@@ -384,5 +385,5 @@
 
     private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) {
-        final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPreset.PresetType.RELATION), presetHandler.getSelection().iterator().next().getKeys(), false);
+        final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(EnumSet.of(TaggingPresetType.RELATION), presetHandler.getSelection().iterator().next().getKeys(), false);
         if (primitives == null)
             return;
Index: trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java	(revision 6068)
@@ -39,4 +39,5 @@
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.TaggingPresetMenu;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetReader;
 import org.openstreetmap.josm.gui.tagging.TaggingPresetSeparator;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
@@ -48,4 +49,5 @@
 
     public static class Factory implements PreferenceSettingFactory {
+        @Override
         public PreferenceSetting createPreferenceSetting() {
             return new TaggingPresetPreference();
@@ -69,4 +71,5 @@
 
     private ValidationListener validationListener = new ValidationListener() {
+        @Override
         public boolean validatePreferences() {
             if (sources.hasActiveSourcesChanged()) {
@@ -78,5 +81,5 @@
                         boolean canLoad = false;
                         try {
-                            TaggingPreset.readAll(source.url, false);
+                            TaggingPresetReader.readAll(source.url, false);
                             canLoad = true;
                         } catch (IOException e) {
@@ -101,5 +104,5 @@
 
                         try {
-                            TaggingPreset.readAll(source.url, true);
+                            TaggingPresetReader.readAll(source.url, true);
                         } catch (IOException e) {
                             // Should not happen, but at least show message
@@ -154,4 +157,5 @@
     };
 
+    @Override
     public void addGui(final PreferenceTabbedPane gui) {
         sortMenu = new JCheckBox(tr("Sort presets menu"),
@@ -258,4 +262,5 @@
     }
 
+    @Override
     public boolean ok() {
         boolean restart = Main.pref.put("taggingpreset.sortmenu", sortMenu.getSelectedObjects() != null);
@@ -269,5 +274,5 @@
      */
     public static void initialize() {
-        taggingPresets = TaggingPreset.readFromPreferences(false);
+        taggingPresets = TaggingPresetReader.readFromPreferences(false);
         for (TaggingPreset tp: taggingPresets) {
             if (!(tp instanceof TaggingPresetSeparator)) {
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 6068)
@@ -193,5 +193,5 @@
     private void updatePresets() {
         presetListPanel.updatePresets(
-                EnumSet.of(TaggingPreset.PresetType.RELATION),
+                EnumSet.of(TaggingPresetType.RELATION),
                 model.getTags(), presetHandler);
         validate();
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java	(revision 6068)
@@ -8,47 +8,20 @@
 import java.awt.Component;
 import java.awt.Dimension;
-import java.awt.Font;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-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.io.UnsupportedEncodingException;
-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.HashSet;
-import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeSet;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
-import javax.swing.ButtonGroup;
 import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JComponent;
 import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JToggleButton;
-import javax.swing.ListCellRenderer;
-import javax.swing.ListModel;
 import javax.swing.SwingUtilities;
 
@@ -61,33 +34,21 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.QuadStateCheckBox;
 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
 import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.preferences.SourceEntry;
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
-import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference.PresetPrefHelper;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPritority;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Link;
+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.gui.widgets.JosmComboBox;
-import org.openstreetmap.josm.gui.widgets.JosmTextField;
-import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Predicate;
-import org.openstreetmap.josm.tools.UrlLabel;
 import org.openstreetmap.josm.tools.Utils;
-import org.openstreetmap.josm.tools.XmlObjectParser;
 import org.openstreetmap.josm.tools.template_engine.ParseError;
 import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
@@ -105,96 +66,4 @@
 public class TaggingPreset extends AbstractAction implements MapView.LayerChangeListener {
 
-    public enum PresetType {
-        NODE(/* ICON */"Mf_node", "node"),
-        WAY(/* ICON */"Mf_way", "way"),
-        RELATION(/* ICON */"Mf_relation", "relation"),
-        CLOSEDWAY(/* ICON */"Mf_closedway", "closedway");
-
-        private final String iconName;
-        private final String name;
-
-        PresetType(String iconName, String name) {
-            this.iconName = iconName;
-            this.name = name;
-        }
-
-        public String getIconName() {
-            return iconName;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public static PresetType forPrimitive(OsmPrimitive p) {
-            return forPrimitiveType(p.getDisplayType());
-        }
-
-        public static PresetType forPrimitiveType(OsmPrimitiveType type) {
-            switch (type) {
-            case NODE:
-                return NODE;
-            case WAY:
-                return WAY;
-            case CLOSEDWAY:
-                return CLOSEDWAY;
-            case RELATION:
-            case MULTIPOLYGON:
-                return RELATION;
-            default:
-                throw new IllegalArgumentException("Unexpected primitive type: " + type);
-            }
-        }
-
-        public static PresetType fromString(String type) {
-            for (PresetType t : PresetType.values()) {
-                if (t.getName().equals(type))
-                    return t;
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Enum denoting how a match (see {@link Item#matches}) is performed.
-     */
-    private 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, negative otherwise.
-         */
-        KEY_VALUE("keyvalue");
-
-        private final String value;
-
-        private MatchType(String value) {
-            this.value = value;
-        }
-
-        public String getValue() {
-            return 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 final int DIALOG_ANSWER_APPLY = 1;
     public static final int DIALOG_ANSWER_NEW_RELATION = 2;
@@ -205,1192 +74,15 @@
     public String name_context;
     public String locale_name;
-    public final static String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
-    private static File zipIcons = null;
-    private static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
-
-    public static abstract class Item {
-
-        protected void initAutoCompletionField(AutoCompletingTextField field, String key) {
-            OsmDataLayer layer = Main.main.getEditLayer();
-            if (layer == null)
-                return;
-            AutoCompletionList list = new AutoCompletionList();
-            Main.main.getEditLayer().data.getAutoCompletionManager().populateWithTagValues(list, key);
-            field.setAutoCompletionList(list);
-        }
-
-        abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel);
-
-        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;
-        }
-    }
-
-    public static abstract class KeyedItem extends Item {
-
-        public String key;
-        public String 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) ? true : null;
-            case KEY_REQUIRED:
-                return tags.containsKey(key);
-            case KEY_VALUE:
-                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
-                    + "]";
-        }
-    }
-
-    public static class Usage {
-        TreeSet<String> values;
-        boolean hadKeys = false;
-        boolean hadEmpty = false;
-        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;
-        }
-    }
-
-    public static final String DIFFERENT = tr("<different>");
-
-    static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
-        Usage returnValue = new Usage();
-        returnValue.values = new TreeSet<String>();
-        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<String>();
-        for (OsmPrimitive s : sel) {
-            String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
-            if (booleanValue != null) {
-                returnValue.values.add(booleanValue);
-            }
-        }
-        return returnValue;
-    }
-
-    public static class PresetListEntry {
-        public String value;
-        public String value_context;
-        public String display_value;
-        public String short_description;
-        public String icon;
-        public String icon_size;
-        public String locale_display_value;
-        public String locale_short_description;
-        private final File zipIcons = TaggingPreset.zipIcons;
-
-        // 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.equals(""))
-                return "&nbsp;";
-
-            final StringBuilder res = new StringBuilder("<b>");
-            res.append(getDisplayValue(true));
-            res.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\">");
-                res.append(getShortDescription(true));
-                res.append("</div>");
-            }
-            return res.toString();
-        }
-
-        public ImageIcon getIcon() {
-            return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size));
-        }
-
-        private Integer parseInteger(String str) {
-            if (str == null || "".equals(str))
-                return null;
-            try {
-                return Integer.parseInt(str);
-            } catch (Exception e) {
-                //
-            }
-            return null;
-        }
-
-        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>
-        }
-    }
-
-    public static class Text extends KeyedItem {
-
-        public String locale_text;
-        public String default_;
-        public String originalValue;
-        public String use_last_as_default = "false";
-        public String auto_increment;
-        public String length;
-
-        private JComponent value;
-
-        @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-
-            // find out if our key is already used in the selection.
-            Usage usage = determineTextUsage(sel, key);
-            AutoCompletingTextField textField = new AutoCompletingTextField();
-            initAutoCompletionField(textField, key);
-            if (length != null && !length.isEmpty()) {
-                textField.setMaxChars(new Integer(length));
-            }
-            if (usage.unused()){
-                if (auto_increment_selected != 0  && auto_increment != null) {
-                    try {
-                        textField.setText(Integer.toString(Integer.parseInt(lastValue.get(key)) + auto_increment_selected));
-                    } catch (NumberFormatException ex) {
-                        // Ignore - cannot auto-increment if last was non-numeric
-                    }
-                }
-                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) && lastValue.containsKey(key)) {
-                        textField.setText(lastValue.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 comboBox = new JosmComboBox(usage.values.toArray());
-                comboBox.setEditable(true);
-                comboBox.setEditor(textField);
-                comboBox.getEditor().setItem(DIFFERENT);
-                value=comboBox;
-                originalValue = DIFFERENT;
-            }
-            if (locale_text == null) {
-                if (text != null) {
-                    if (text_context != null) {
-                        locale_text = trc(text_context, fixPresetString(text));
-                    } else {
-                        locale_text = tr(fixPresetString(text));
-                    }
-                }
-            }
-
-            // 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));
-                    bg.add(aibutton);
-                    try {
-                        // TODO there must be a better way to parse a number like "+3" than this.
-                        final int buttonvalue = ((Number)NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue();
-                        if (auto_increment_selected == buttonvalue) aibutton.setSelected(true);
-                        aibutton.addActionListener(new ActionListener() {
-                            public void actionPerformed(ActionEvent e) {
-                                auto_increment_selected = buttonvalue;
-                            }
-                        });
-                        pnl.add(aibutton, GBC.std());
-                    } catch (ParseException x) {
-                        System.err.println("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);
-                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.addActionListener(new ActionListener() {
-                    public void actionPerformed(ActionEvent e) {
-                        auto_increment_selected = 0;
-                        clearbutton.setSelected(true);
-                    }
-                });
-                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 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) {
-                System.err.println("No 'last value' support for component " + value);
-                return;
-            }
-            
-            v = v.trim();
-
-            if (!"false".equals(use_last_as_default) || auto_increment != null) {
-                lastValue.put(key, v);
-            }
-            if (v.equals(originalValue) || (originalValue == null && v.length() == 0))
-                return;
-
-            changedTags.add(new Tag(key, v));
-        }
-
-        @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_);
-        }
-    }
-
-    public static class Check extends KeyedItem {
-
-        public String locale_text;
-        public String value_on = OsmUtils.trueval;
-        public String value_off = OsmUtils.falseval;
-        public boolean default_ = false; // only used for tagless objects
-
-        private QuadStateCheckBox check;
-        private QuadStateCheckBox.State initialState;
-        private boolean def;
-
-        @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-
-            // find out if our key is already used in the selection.
-            Usage usage = determineBooleanUsage(sel, key);
-            def = default_;
-
-            if(locale_text == null) {
-                if(text_context != null) {
-                    locale_text = trc(text_context, fixPresetString(text));
-                } else {
-                    locale_text = tr(fixPresetString(text));
-                }
-            }
-
-            String oneValue = null;
-            for (String s : usage.values) {
-                oneValue = s;
-            }
-            if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
-                if (def && !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 = false;
-                        }
-                }
-
-                // 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) ?
-                        QuadStateCheckBox.State.SELECTED :
-                            value_off.equals(oneValue) ?
-                                    QuadStateCheckBox.State.NOT_SELECTED :
-                                        def ? QuadStateCheckBox.State.SELECTED
-                                                : QuadStateCheckBox.State.UNSET;
-                check = new QuadStateCheckBox(locale_text, initialState,
-                        new QuadStateCheckBox.State[] {
-                        QuadStateCheckBox.State.SELECTED,
-                        QuadStateCheckBox.State.NOT_SELECTED,
-                        QuadStateCheckBox.State.UNSET });
-            } else {
-                def = false;
-                // 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;
-                check = new QuadStateCheckBox(locale_text, QuadStateCheckBox.State.PARTIAL,
-                        new QuadStateCheckBox.State[] {
-                        QuadStateCheckBox.State.PARTIAL,
-                        QuadStateCheckBox.State.SELECTED,
-                        QuadStateCheckBox.State.NOT_SELECTED,
-                        QuadStateCheckBox.State.UNSET });
-            }
-            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) 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 Arrays.asList(value_on, value_off);
-        }
-    }
-
-    public static abstract class ComboMultiSelect extends KeyedItem {
-
-        public String locale_text;
-        public String values;
-        public String values_from;
-        public String values_context;
-        public String display_values;
-        public String locale_display_values;
-        public String short_descriptions;
-        public String locale_short_descriptions;
-        public String default_;
-        public String delimiter = ";";
-        public String use_last_as_default = "false";
-
-        protected JComponent component;
-        protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<String, PresetListEntry>();
-        private boolean initialized = false;
-        protected Usage usage;
-        protected Object originalValue;
-
-        protected abstract Object getSelectedItem();
-        protected abstract void addToPanelAnchor(JPanel p, String def);
-
-        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) {
-
-            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_);
-
-            return true;
-
-        }
-
-        private void initListEntries() {
-            if (initialized) {
-                lhm.remove(DIFFERENT); // possibly added in #addToPanel
-                return;
-            } else if (lhm.isEmpty()) {
-                initListEntriesFromAttributes();
-            } else {
-                if (values != null) {
-                    System.err.println(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) {
-                    System.err.println(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) {
-                    System.err.println(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 = trc(text_context, fixPresetString(text));
-            }
-            initialized = true;
-        }
-
-        private String[] 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 {
-                            System.err.println(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,
-                                    "public static String[] methodName()"));
-                        }
-                    } catch (Exception e) {
-                        System.err.println(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) {
-                System.err.println(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) {
-                System.err.println(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;
-            }
-
-            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]));
-                        }
-                        lhm.put(value_array[i], e);
-                        display_array[i] = e.getDisplayValue(true);
-            }
-
-            return display_array;
-        }
-
-        protected String getDisplayIfNull(String display) {
-            return display;
-        }
-
-        @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(display);
-            }
-
-            if (display != null) {
-                for (String key : lhm.keySet()) {
-                    String k = lhm.get(key).toString();
-                    if (k != null && k.equals(display)) {
-                        value = key;
-                        break;
-                    }
-                }
-                if (value == null) {
-                    value = display;
-                }
-            } else {
-                value = "";
-            }
-            value = value.trim();
-
-            // no change if same as before
-            if (originalValue == null) {
-                if (value.length() == 0)
-                    return;
-            } else if (value.equals(originalValue.toString()))
-                return;
-
-            if (!"false".equals(use_last_as_default)) {
-                lastValue.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 ListCellRenderer RENDERER = new ListCellRenderer() {
-
-            JLabel lbl = new JLabel();
-
-            public Component getListCellRendererComponent(
-                    JList list,
-                    Object value,
-                    int index,
-                    boolean isSelected,
-                    boolean cellHasFocus) {
-                PresetListEntry item = (PresetListEntry) value;
-
-                // 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 getListCellRenderer() {
-            return RENDERER;
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.NONE;
-        }
-    }
-
-    public static class Combo extends ComboMultiSelect {
-
-        public boolean editable = true;
-        protected JosmComboBox combo;
-        public String length;
-
-        public Combo() {
-            delimiter = ",";
-        }
-
-        @Override
-        protected void addToPanelAnchor(JPanel p, String def) {
-            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());
-            component = combo;
-            combo.setRenderer(getListCellRenderer());
-            combo.setEditable(editable);
-            combo.reinitialize(lhm.values());
-            AutoCompletingTextField tf = new AutoCompletingTextField();
-            initAutoCompletionField(tf, key);
-            if (length != null && !length.isEmpty()) {
-                tf.setMaxChars(new Integer(length));
-            }
-            AutoCompletionList acList = tf.getAutoCompletionList();
-            if (acList != null) {
-                acList.add(getDisplayValues(), AutoCompletionItemPritority.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) && lastValue.containsKey(key)) {
-                    combo.setSelectedItem(lhm.get(lastValue.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(String display) {
-            if (combo.isEditable())
-                return combo.getEditor().getItem().toString();
-            else
-                return display;
-
-        }
-    }
-
-    /**
-     * Class that allows list values to be assigned and retrieved as a comma-delimited
-     * string.
-     */
-    public static class ConcatenatingJList extends JList {
-        private String delimiter;
-        public ConcatenatingJList(String del, Object[] o) {
-            super(o);
-            delimiter = del;
-        }
-        public void setSelectedItem(Object o) {
-            if (o == null) {
-                clearSelection();
-            } else {
-                String s = o.toString();
-                TreeSet<String> parts = new TreeSet<String>(Arrays.asList(s.split(delimiter)));
-                ListModel lm = getModel();
-                int[] intParts = new int[lm.getSize()];
-                int j = 0;
-                for (int i = 0; i < lm.getSize(); i++) {
-                    if (parts.contains((((PresetListEntry)lm.getElementAt(i)).value))) {
-                        intParts[j++]=i;
-                    }
-                }
-                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(Utils.join(delimiter, parts).equals(getSelectedItem()));
-            }
-        }
-        public String getSelectedItem() {
-            ListModel 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(((PresetListEntry)lm.getElementAt(si[i])).value);
-            }
-            return builder.toString();
-        }
-    }
-
-    public static class MultiSelect extends ComboMultiSelect {
-
-        public long rows = -1;
-        protected ConcatenatingJList list;
-
-        @Override
-        protected void addToPanelAnchor(JPanel p, String def) {
-            list = new ConcatenatingJList(delimiter, lhm.values().toArray());
-            component = list;
-            ListCellRenderer 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 != -1) {
-                double height = renderer.getListCellRendererComponent(list,
-                        new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * 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);
-            }
-        }
-    }
-
-    /**
-     * allow escaped comma in comma separated list:
-     * "A\, B\, C,one\, two" --> ["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<String>();
-        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()]);
-    }
-
-    public static class Label extends Item {
-
-        public String text;
-        public String text_context;
-        public String locale_text;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            if (locale_text == null) {
-                if (text_context != null) {
-                    locale_text = trc(text_context, fixPresetString(text));
-                } else {
-                    locale_text = tr(fixPresetString(text));
-                }
-            }
-            p.add(new JLabel(locale_text), GBC.eol());
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Link extends Item {
-
-        public String href;
-        public String text;
-        public String text_context;
-        public String locale_text;
-        public String locale_href;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            if (locale_text == null) {
-                if (text == null) {
-                    locale_text = tr("More information about this feature");
-                } else if (text_context != null) {
-                    locale_text = trc(text_context, fixPresetString(text));
-                } else {
-                    locale_text = tr(fixPresetString(text));
-                }
-            }
-            String url = locale_href;
-            if (url == null) {
-                url = href;
-            }
-            if (url != null) {
-                p.add(new UrlLabel(url, locale_text, 2), GBC.eol().anchor(GBC.WEST));
-            }
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Role {
-        public EnumSet<PresetType> types;
-        public String key;
-        public String text;
-        public String text_context;
-        public String locale_text;
-        public Match memberExpression;
-
-        public boolean required = false;
-        public long count = 0;
-
-        public void setType(String types) throws SAXException {
-            this.types = TaggingPreset.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 {
-                this.memberExpression = SearchCompiler.compile(member_expression, true, true);
-            } catch (SearchCompiler.ParseError ex) {
-                throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
-            }
-        }
-
-        /* 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, Collection<OsmPrimitive> sel) {
-            String cstring;
-            if(count > 0 && !required) {
-                cstring = "0,"+String.valueOf(count);
-            } else if(count > 0) {
-                cstring = String.valueOf(count);
-            } else if(!required) {
-                cstring = "0-...";
-            } else {
-                cstring = "1-...";
-            }
-            if(locale_text == null) {
-                if (text != null) {
-                    if(text_context != null) {
-                        locale_text = trc(text_context, fixPresetString(text));
-                    } else {
-                        locale_text = tr(fixPresetString(text));
-                    }
-                }
-            }
-            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(PresetType t : types) {
-                    pp.add(new JLabel(ImageProvider.get(t.getIconName())));
-                }
-                p.add(pp, GBC.eol());
-            }
-            return true;
-        }
-    }
-
-    public static class Roles extends Item {
-
-        public final List<Role> roles = new LinkedList<Role>();
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            p.add(new JLabel(" "), GBC.eol()); // space
-            if (roles.size() > 0) {
-                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, sel);
-                }
-                p.add(proles, GBC.eol());
-            }
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Optional extends Item {
-
-        // TODO: Draw a box around optional stuff
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            p.add(new JLabel(" "), GBC.eol()); // space
-            p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
-            p.add(new JLabel(" "), GBC.eol()); // space
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Space extends Item {
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            p.add(new JLabel(" "), GBC.eol()); // space
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-        }
-    }
-
-    public static class Key extends KeyedItem {
-
-        public String value;
-
-        @Override
-        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
-            return false;
-        }
-
-        @Override
-        public void addCommands(List<Tag> changedTags) {
-            changedTags.add(new Tag(key, value));
-        }
-
-        @Override
-        public MatchType getDefaultMatch() {
-            return MatchType.KEY_VALUE;
-        }
-
-        @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
-                    + "]";
-        }
-    }
-
+   public final static String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
+    
     /**
      * The types as preparsed collection.
      */
-    public EnumSet<PresetType> types;
-    public List<Item> data = new LinkedList<Item>();
+    public EnumSet<TaggingPresetType> types;
+    public List<TaggingPresetItem> data = new LinkedList<TaggingPresetItem>();
     public Roles roles;
     public TemplateEntry nameTemplate;
     public Match nameTemplateFilter;
-    private static final HashMap<String,String> lastValue = new HashMap<String,String>();
-    private static int auto_increment_selected = 0;
-
+    
     /**
      * Create an empty tagging preset. This will not have any items and
@@ -1417,7 +109,7 @@
         if(locale_name == null) {
             if(name_context != null) {
-                locale_name = trc(name_context, fixPresetString(name));
+                locale_name = trc(name_context, TaggingPresetItems.fixPresetString(name));
             } else {
-                locale_name = tr(fixPresetString(name));
+                locale_name = tr(TaggingPresetItems.fixPresetString(name));
             }
         }
@@ -1430,13 +122,4 @@
     public String getRawName() {
         return group != null ? group.getRawName() + "/" + name : name;
-    }
-
-    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();
     }
 
@@ -1453,5 +136,5 @@
         imgProv.setDirs(s);
         imgProv.setId("presets");
-        imgProv.setArchive(TaggingPreset.zipIcons);
+        imgProv.setArchive(TaggingPresetReader.getZipIcons());
         imgProv.setOptional(true);
         imgProv.setMaxWidth(16).setMaxHeight(16);
@@ -1473,29 +156,9 @@
     }
 
-    // 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,EnumSet<PresetType>> typeCache =
-            new LinkedHashMap<String, EnumSet<PresetType>>(16, 1.1f, true);
-
-    static public EnumSet<PresetType> getType(String types) throws SAXException {
-        if (typeCache.containsKey(types))
-            return typeCache.get(types);
-        EnumSet<PresetType> result = EnumSet.noneOf(PresetType.class);
-        for (String type : Arrays.asList(types.split(","))) {
-            try {
-                PresetType presetType = PresetType.fromString(type);
-                result.add(presetType);
-            } catch (IllegalArgumentException e) {
-                throw new SAXException(tr("Unknown type: {0}", type));
-            }
-        }
-        typeCache.put(types, result);
-        return result;
-    }
-
     /*
      * Called from the XML parser to set the types this preset affects.
      */
     public void setType(String types) throws SAXException {
-        this.types = getType(types);
+        this.types = TaggingPresetItems.getType(types);
     }
 
@@ -1518,159 +181,4 @@
     }
 
-
-    public static List<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
-        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.mapOnStart("optional", Optional.class);
-        parser.mapOnStart("roles", Roles.class);
-        parser.map("role", Role.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", PresetListEntry.class);
-        LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
-        TaggingPresetMenu lastmenu = null;
-        Roles lastrole = null;
-        List<PresetListEntry> listEntries = new LinkedList<PresetListEntry>();
-
-        if (validate) {
-            parser.startWithValidation(in, "http://josm.openstreetmap.de/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
-        } else {
-            parser.start(in);
-        }
-        while(parser.hasNext()) {
-            Object o = parser.next();
-            if (o instanceof TaggingPresetMenu) {
-                TaggingPresetMenu tp = (TaggingPresetMenu) o;
-                if(tp == lastmenu) {
-                    lastmenu = tp.group;
-                } else
-                {
-                    tp.group = lastmenu;
-                    tp.setDisplayName();
-                    lastmenu = tp;
-                    all.add(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.size() != 0) {
-                    if (o instanceof Roles) {
-                        all.getLast().data.add((Item) 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 PresetListEntry) {
-                        listEntries.add((PresetListEntry) o);
-                    } else {
-                        all.getLast().data.add((Item) 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<PresetListEntry>();
-                        lastrole = null;
-                    }
-                } else
-                    throw new SAXException(tr("Preset sub element without parent"));
-            }
-        }
-        return all;
-    }
-
-    public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
-        Collection<TaggingPreset> tp;
-        MirroredInputStream s = new MirroredInputStream(source);
-        try {
-            InputStream zip = s.getZipEntry("xml","preset");
-            if(zip != null) {
-                zipIcons = s.getFile();
-            }
-            InputStreamReader r;
-            try {
-                r = new InputStreamReader(zip == null ? s : zip, "UTF-8");
-            } catch (UnsupportedEncodingException e) {
-                r = new InputStreamReader(zip == null ? s: zip);
-            }
-            try {
-                tp = TaggingPreset.readAll(new BufferedReader(r), validate);
-            } finally {
-                Utils.close(r);
-            }
-        } finally {
-            Utils.close(s);
-        }
-        return tp;
-    }
-
-    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
-        LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
-        for(String source : sources)  {
-            try {
-                allPresets.addAll(TaggingPreset.readAll(source, validate));
-            } catch (IOException e) {
-                System.err.println(e.getClass().getName()+": "+e.getMessage());
-                System.err.println(source);
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("Could not read tagging preset source: {0}",source),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                        );
-            } catch (SAXException e) {
-                System.err.println(e.getClass().getName()+": "+e.getMessage());
-                System.err.println(source);
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("Error parsing {0}: ", source)+e.getMessage(),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                        );
-            }
-        }
-        return allPresets;
-    }
-
-    public static LinkedList<String> getPresetSources() {
-        LinkedList<String> sources = new LinkedList<String>();
-
-        for (SourceEntry e : (new PresetPrefHelper()).get()) {
-            sources.add(e.url);
-        }
-
-        return sources;
-    }
-
-    public static Collection<TaggingPreset> readFromPreferences(boolean validate) {
-        return readAll(getPresetSources(), validate);
-    }
-
     private static class PresetPanel extends JPanel {
         boolean hasElements = false;
@@ -1685,8 +193,8 @@
             return null;
         PresetPanel p = new PresetPanel();
-        LinkedList<Item> l = new LinkedList<Item>();
+        LinkedList<TaggingPresetItem> l = new LinkedList<TaggingPresetItem>();
         if(types != null){
             JPanel pp = new JPanel();
-            for(PresetType t : types){
+            for(TaggingPresetType t : types){
                 JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
                 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
@@ -1697,5 +205,5 @@
 
         JPanel items = new JPanel(new GridBagLayout());
-        for (Item i : data){
+        for (TaggingPresetItem i : data){
             if(i instanceof Link) {
                 l.add(i);
@@ -1711,5 +219,5 @@
         }
 
-        for(Item link : l) {
+        for(TaggingPresetItem link : l) {
             link.addToPanel(p, selected);
         }
@@ -1720,7 +228,7 @@
     public boolean isShowable()
     {
-        for(Item i : data)
+        for(TaggingPresetItem i : data)
         {
-            if(!(i instanceof Optional || i instanceof Space || i instanceof Key))
+            if(!(i instanceof TaggingPresetItems.Optional || i instanceof TaggingPresetItems.Space || i instanceof TaggingPresetItems.Key))
                 return true;
         }
@@ -1732,5 +240,5 @@
             for (Role i : roles.roles) {
                 if (i.memberExpression != null && i.memberExpression.match(osm) 
-                        && (i.types == null || i.types.isEmpty() || i.types.contains(PresetType.forPrimitive(osm)) )) {
+                        && (i.types == null || i.types.isEmpty() || i.types.contains(TaggingPresetType.forPrimitive(osm)) )) {
                     return i.key;
                 }
@@ -1740,4 +248,5 @@
     }
 
+    @Override
     public void actionPerformed(ActionEvent e) {
         if (Main.main == null) return;
@@ -1747,5 +256,5 @@
         int answer = showDialog(sel, supportsRelation());
 
-        if (sel.size() != 0 && answer == DIALOG_ANSWER_APPLY) {
+        if (!sel.isEmpty() && answer == DIALOG_ANSWER_APPLY) {
             Command cmd = createCommand(sel, getChangedTags());
             if (cmd != null) {
@@ -1781,7 +290,7 @@
 
         int answer = 1;
-        if (p.getComponentCount() != 0 && (sel.size() == 0 || p.hasElements)) {
+        if (p.getComponentCount() != 0 && (sel.isEmpty() || p.hasElements)) {
             String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
-            if(sel.size() == 0) {
+            if(sel.isEmpty()) {
                 if(originalSelectionEmpty) {
                     title = tr("Nothing selected!");
@@ -1849,6 +358,6 @@
                 if(osm instanceof Relation)
                 {
-                    if(!types.contains(PresetType.RELATION) &&
-                            !(types.contains(PresetType.CLOSEDWAY) && ((Relation)osm).isMultipolygon())) {
+                    if(!types.contains(TaggingPresetType.RELATION) &&
+                            !(types.contains(TaggingPresetType.CLOSEDWAY) && ((Relation)osm).isMultipolygon())) {
                         continue;
                     }
@@ -1856,5 +365,5 @@
                 else if(osm instanceof Node)
                 {
-                    if(!types.contains(PresetType.NODE)) {
+                    if(!types.contains(TaggingPresetType.NODE)) {
                         continue;
                     }
@@ -1862,6 +371,6 @@
                 else if(osm instanceof Way)
                 {
-                    if(!types.contains(PresetType.WAY) &&
-                            !(types.contains(PresetType.CLOSEDWAY) && ((Way)osm).isClosed())) {
+                    if(!types.contains(TaggingPresetType.WAY) &&
+                            !(types.contains(TaggingPresetType.CLOSEDWAY) && ((Way)osm).isClosed())) {
                         continue;
                     }
@@ -1875,12 +384,8 @@
     public List<Tag> getChangedTags() {
         List<Tag> result = new ArrayList<Tag>();
-        for (Item i: data) {
+        for (TaggingPresetItem i: data) {
             i.addCommands(result);
         }
         return result;
-    }
-
-    private static String fixPresetString(String s) {
-        return s == null ? s : s.replaceAll("'","''");
     }
 
@@ -1900,5 +405,5 @@
 
     private boolean supportsRelation() {
-        return types == null || types.contains(PresetType.RELATION);
+        return types == null || types.contains(TaggingPresetType.RELATION);
     }
 
@@ -1907,12 +412,15 @@
     }
 
+    @Override
     public void activeLayerChange(Layer oldLayer, Layer newLayer) {
         updateEnabledState();
     }
 
+    @Override
     public void layerAdded(Layer newLayer) {
         updateEnabledState();
     }
 
+    @Override
     public void layerRemoved(Layer oldLayer) {
         updateEnabledState();
@@ -1924,9 +432,9 @@
     }
 
-    public boolean typeMatches(Collection<PresetType> t) {
+    public boolean typeMatches(Collection<TaggingPresetType> t) {
         return t == null || types == null || types.containsAll(t);
     }
 
-    public boolean matches(Collection<PresetType> t, Map<String, String> tags, boolean onlyShowable) {
+    public boolean matches(Collection<TaggingPresetType> t, Map<String, String> tags, boolean onlyShowable) {
         if (onlyShowable && !isShowable())
             return false;
@@ -1934,5 +442,5 @@
             return false;
         boolean atLeastOnePositiveMatch = false;
-        for (Item item : data) {
+        for (TaggingPresetItem item : data) {
             Boolean m = item.matches(tags);
             if (m != null && !m)
@@ -1945,5 +453,5 @@
     }
 
-    public static Collection<TaggingPreset> getMatchingPresets(final Collection<PresetType> t, final Map<String, String> tags, final boolean onlyShowable) {
+    public static Collection<TaggingPreset> getMatchingPresets(final Collection<TaggingPresetType> t, final Map<String, String> tags, final boolean onlyShowable) {
         return Utils.filter(TaggingPresetPreference.taggingPresets, new Predicate<TaggingPreset>() {
             @Override
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItem.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItem.java	(revision 6068)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItem.java	(revision 6068)
@@ -0,0 +1,49 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.gui.tagging;
+
+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;
+
+/**
+ * This calss reperesentse single part of a preset - one field or text label that
+ * is shown to user
+ */
+public abstract class TaggingPresetItem {
+
+    protected void initAutoCompletionField(AutoCompletingTextField field, String key) {
+        OsmDataLayer layer = Main.main.getEditLayer();
+        if (layer == null) {
+            return;
+        }
+        AutoCompletionList list = new AutoCompletionList();
+        Main.main.getEditLayer().data.getAutoCompletionManager().populateWithTagValues(list, key);
+        field.setAutoCompletionList(list);
+    }
+
+    abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel);
+
+    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: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java	(revision 6068)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java	(revision 6068)
@@ -0,0 +1,1293 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+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.TreeSet;
+import javax.swing.ButtonGroup;
+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.JToggleButton;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import org.xml.sax.SAXException;
+
+import org.openstreetmap.josm.Main;
+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.QuadStateCheckBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPritority;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.UrlLabel;
+import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.tools.GBC;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+
+/**
+ * Class that constains all subtypes of TaggingPresetItem,
+ * static supplementary data, types and methods
+ */
+public final class TaggingPresetItems {
+    private TaggingPresetItems() {    }
+    
+    private static int auto_increment_selected = 0;
+    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,EnumSet<TaggingPresetType>> typeCache =
+            new LinkedHashMap<String, EnumSet<TaggingPresetType>>(16, 1.1f, true);
+    
+    /**
+     * Last value of each key used in presets, used for prefilling corresponding fields
+     */
+    private static final HashMap<String,String> lastValue = new HashMap<String,String>();
+
+    public static class PresetListEntry {
+        public String value;
+        public String value_context;
+        public String display_value;
+        public String short_description;
+        public String icon;
+        public String icon_size;
+        public String locale_display_value;
+        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.equals(""))
+                return "&nbsp;";
+
+            final StringBuilder res = new StringBuilder("<b>");
+            res.append(getDisplayValue(true));
+            res.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\">");
+                res.append(getShortDescription(true));
+                res.append("</div>");
+            }
+            return res.toString();
+        }
+
+        public ImageIcon getIcon() {
+            return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size));
+        }
+
+        private Integer parseInteger(String str) {
+            if (str == null || "".equals(str))
+                return null;
+            try {
+                return Integer.parseInt(str);
+            } catch (Exception e) {
+                //
+            }
+            return null;
+        }
+
+        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>
+        }
+    }
+
+    public static class Role {
+        public EnumSet<TaggingPresetType> types;
+        public String key;
+        public String text;
+        public String text_context;
+        public String locale_text;
+        public SearchCompiler.Match memberExpression;
+
+        public boolean required = false;
+        public long count = 0;
+
+        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 {
+                this.memberExpression = SearchCompiler.compile(member_expression, true, true);
+            } catch (SearchCompiler.ParseError ex) {
+                throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
+            }
+        }
+
+        /* 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, Collection<OsmPrimitive> sel) {
+            String cstring;
+            if(count > 0 && !required) {
+                cstring = "0,"+String.valueOf(count);
+            } else if(count > 0) {
+                cstring = String.valueOf(count);
+            } else if(!required) {
+                cstring = "0-...";
+            } else {
+                cstring = "1-...";
+            }
+            if(locale_text == null) {
+                if (text != null) {
+                    if(text_context != null) {
+                        locale_text = trc(text_context, fixPresetString(text));
+                    } else {
+                        locale_text = tr(fixPresetString(text));
+                    }
+                }
+            }
+            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 Item#matches}) is performed.
+     */
+    public static 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, negative otherwise.
+         */
+        KEY_VALUE("keyvalue");
+
+        private final String value;
+
+        private MatchType(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return 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 {
+        TreeSet<String> values;
+        boolean hadKeys = false;
+        boolean hadEmpty = false;
+        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;
+        }
+    }
+
+    public static class Label extends TaggingPresetItem {
+
+        public String text;
+        public String text_context;
+        public String locale_text;
+
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            if (locale_text == null) {
+                if (text_context != null) {
+                    locale_text = trc(text_context, fixPresetString(text));
+                } else {
+                    locale_text = tr(fixPresetString(text));
+                }
+            }
+            p.add(new JLabel(locale_text), GBC.eol());
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+        }
+    }
+
+    public static class Link extends TaggingPresetItem {
+
+        public String href;
+        public String text;
+        public String text_context;
+        public String locale_text;
+        public String locale_href;
+
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            if (locale_text == null) {
+                if (text == null) {
+                    locale_text = tr("More information about this feature");
+                } else if (text_context != null) {
+                    locale_text = trc(text_context, fixPresetString(text));
+                } else {
+                    locale_text = tr(fixPresetString(text));
+                }
+            }
+            String url = locale_href;
+            if (url == null) {
+                url = href;
+            }
+            if (url != null) {
+                p.add(new UrlLabel(url, locale_text, 2), GBC.eol().anchor(GBC.WEST));
+            }
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+        }
+    }
+    
+    public static class Roles extends TaggingPresetItem {
+
+        public final List<Role> roles = new LinkedList<Role>();
+
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            p.add(new JLabel(" "), GBC.eol()); // space
+            if (roles.size() > 0) {
+                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, sel);
+                }
+                p.add(proles, GBC.eol());
+            }
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+        }
+    }
+
+    public static class Optional extends TaggingPresetItem {
+
+        // TODO: Draw a box around optional stuff
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            p.add(new JLabel(" "), GBC.eol()); // space
+            p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
+            p.add(new JLabel(" "), GBC.eol()); // space
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+        }
+    }
+
+    public static class Space extends TaggingPresetItem {
+
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            p.add(new JLabel(" "), GBC.eol()); // space
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+        }
+    }
+
+
+    public static abstract class KeyedItem extends TaggingPresetItem {
+
+        public String key;
+        public String 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) ? true : null;
+            case KEY_REQUIRED:
+                return tags.containsKey(key);
+            case KEY_VALUE:
+                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
+                    + "]";
+        }
+    }
+
+    public static class Key extends KeyedItem {
+
+        public String value;
+
+        @Override
+        public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+            return false;
+        }
+
+        @Override
+        public void addCommands(List<Tag> changedTags) {
+            changedTags.add(new Tag(key, value));
+        }
+
+        @Override
+        public MatchType getDefaultMatch() {
+            return MatchType.KEY_VALUE;
+        }
+
+        @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
+                    + "]";
+        }
+    }
+    
+    public static class Text extends KeyedItem {
+
+        public String locale_text;
+        public String default_;
+        public String originalValue;
+        public String use_last_as_default = "false";
+        public String auto_increment;
+        public String length;
+
+        private JComponent value;
+
+        @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+
+            // find out if our key is already used in the selection.
+            Usage usage = determineTextUsage(sel, key);
+            AutoCompletingTextField textField = new AutoCompletingTextField();
+            initAutoCompletionField(textField, key);
+            if (length != null && !length.isEmpty()) {
+                textField.setMaxChars(new Integer(length));
+            }
+            if (usage.unused()){
+                if (auto_increment_selected != 0  && auto_increment != null) {
+                    try {
+                        textField.setText(Integer.toString(Integer.parseInt(lastValue.get(key)) + auto_increment_selected));
+                    } catch (NumberFormatException ex) {
+                        // Ignore - cannot auto-increment if last was non-numeric
+                    }
+                }
+                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) && lastValue.containsKey(key)) {
+                        textField.setText(lastValue.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 comboBox = new JosmComboBox(usage.values.toArray());
+                comboBox.setEditable(true);
+                comboBox.setEditor(textField);
+                comboBox.getEditor().setItem(DIFFERENT);
+                value=comboBox;
+                originalValue = DIFFERENT;
+            }
+            if (locale_text == null) {
+                if (text != null) {
+                    if (text_context != null) {
+                        locale_text = trc(text_context, fixPresetString(text));
+                    } else {
+                        locale_text = tr(fixPresetString(text));
+                    }
+                }
+            }
+
+            // 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));
+                    bg.add(aibutton);
+                    try {
+                        // TODO there must be a better way to parse a number like "+3" than this.
+                        final int buttonvalue = ((Number)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) {
+                        System.err.println("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);
+                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.addActionListener(new ActionListener() {
+                    @Override
+                    public void actionPerformed(ActionEvent e) {
+                        auto_increment_selected = 0;
+                        clearbutton.setSelected(true);
+                    }
+                });
+                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 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) {
+                System.err.println("No 'last value' support for component " + value);
+                return;
+            }
+            
+            v = v.trim();
+
+            if (!"false".equals(use_last_as_default) || auto_increment != null) {
+                lastValue.put(key, v);
+            }
+            if (v.equals(originalValue) || (originalValue == null && v.length() == 0))
+                return;
+
+            changedTags.add(new Tag(key, v));
+        }
+
+        @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_);
+        }
+    }
+
+    public static class Check extends KeyedItem {
+
+        public String locale_text;
+        public String value_on = OsmUtils.trueval;
+        public String value_off = OsmUtils.falseval;
+        public boolean default_ = false; // only used for tagless objects
+
+        private QuadStateCheckBox check;
+        private QuadStateCheckBox.State initialState;
+        private boolean def;
+
+        @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
+
+            // find out if our key is already used in the selection.
+            Usage usage = determineBooleanUsage(sel, key);
+            def = default_;
+
+            if(locale_text == null) {
+                if(text_context != null) {
+                    locale_text = trc(text_context, fixPresetString(text));
+                } else {
+                    locale_text = tr(fixPresetString(text));
+                }
+            }
+
+            String oneValue = null;
+            for (String s : usage.values) {
+                oneValue = s;
+            }
+            if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
+                if (def && !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 = false;
+                        }
+                }
+
+                // 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) ?
+                        QuadStateCheckBox.State.SELECTED :
+                            value_off.equals(oneValue) ?
+                                    QuadStateCheckBox.State.NOT_SELECTED :
+                                        def ? QuadStateCheckBox.State.SELECTED
+                                                : QuadStateCheckBox.State.UNSET;
+                check = new QuadStateCheckBox(locale_text, initialState,
+                        new QuadStateCheckBox.State[] {
+                        QuadStateCheckBox.State.SELECTED,
+                        QuadStateCheckBox.State.NOT_SELECTED,
+                        QuadStateCheckBox.State.UNSET });
+            } else {
+                def = false;
+                // 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;
+                check = new QuadStateCheckBox(locale_text, QuadStateCheckBox.State.PARTIAL,
+                        new QuadStateCheckBox.State[] {
+                        QuadStateCheckBox.State.PARTIAL,
+                        QuadStateCheckBox.State.SELECTED,
+                        QuadStateCheckBox.State.NOT_SELECTED,
+                        QuadStateCheckBox.State.UNSET });
+            }
+            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) 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 Arrays.asList(value_on, value_off);
+        }
+    }
+
+    public static abstract class ComboMultiSelect extends KeyedItem {
+
+        public String locale_text;
+        public String values;
+        public String values_from;
+        public String values_context;
+        public String display_values;
+        public String locale_display_values;
+        public String short_descriptions;
+        public String locale_short_descriptions;
+        public String default_;
+        public String delimiter = ";";
+        public String use_last_as_default = "false";
+
+        protected JComponent component;
+        protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<String, PresetListEntry>();
+        private boolean initialized = false;
+        protected Usage usage;
+        protected Object originalValue;
+
+        protected abstract Object getSelectedItem();
+        protected abstract void addToPanelAnchor(JPanel p, String def);
+
+        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) {
+
+            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_);
+
+            return true;
+
+        }
+
+        private void initListEntries() {
+            if (initialized) {
+                lhm.remove(DIFFERENT); // possibly added in #addToPanel
+                return;
+            } else if (lhm.isEmpty()) {
+                initListEntriesFromAttributes();
+            } else {
+                if (values != null) {
+                    System.err.println(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) {
+                    System.err.println(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) {
+                    System.err.println(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 = trc(text_context, fixPresetString(text));
+            }
+            initialized = true;
+        }
+
+        private String[] 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 {
+                            System.err.println(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,
+                                    "public static String[] methodName()"));
+                        }
+                    } catch (Exception e) {
+                        System.err.println(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) {
+                System.err.println(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) {
+                System.err.println(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;
+            }
+
+            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]));
+                        }
+                        lhm.put(value_array[i], e);
+                        display_array[i] = e.getDisplayValue(true);
+            }
+
+            return display_array;
+        }
+
+        protected String getDisplayIfNull(String display) {
+            return display;
+        }
+
+        @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(display);
+            }
+
+            if (display != null) {
+                for (String val : lhm.keySet()) {
+                    String k = lhm.get(val).toString();
+                    if (k != null && k.equals(display)) {
+                        value = val;
+                        break;
+                    }
+                }
+                if (value == null) {
+                    value = display;
+                }
+            } else {
+                value = "";
+            }
+            value = value.trim();
+
+            // no change if same as before
+            if (originalValue == null) {
+                if (value.length() == 0)
+                    return;
+            } else if (value.equals(originalValue.toString()))
+                return;
+
+            if (!"false".equals(use_last_as_default)) {
+                lastValue.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 ListCellRenderer RENDERER = new ListCellRenderer() {
+
+            JLabel lbl = new JLabel();
+
+            @Override
+            public Component getListCellRendererComponent(
+                    JList list,
+                    Object value,
+                    int index,
+                    boolean isSelected,
+                    boolean cellHasFocus) {
+                PresetListEntry item = (PresetListEntry) value;
+
+                // 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 getListCellRenderer() {
+            return RENDERER;
+        }
+
+        @Override
+        public MatchType getDefaultMatch() {
+            return MatchType.NONE;
+        }
+    }
+
+    public static class Combo extends ComboMultiSelect {
+
+        public boolean editable = true;
+        protected JosmComboBox combo;
+        public String length;
+
+        public Combo() {
+            delimiter = ",";
+        }
+
+        @Override
+        protected void addToPanelAnchor(JPanel p, String def) {
+            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());
+            component = combo;
+            combo.setRenderer(getListCellRenderer());
+            combo.setEditable(editable);
+            combo.reinitialize(lhm.values());
+            AutoCompletingTextField tf = new AutoCompletingTextField();
+            initAutoCompletionField(tf, key);
+            if (length != null && !length.isEmpty()) {
+                tf.setMaxChars(new Integer(length));
+            }
+            AutoCompletionList acList = tf.getAutoCompletionList();
+            if (acList != null) {
+                acList.add(getDisplayValues(), AutoCompletionItemPritority.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) && lastValue.containsKey(key)) {
+                    combo.setSelectedItem(lhm.get(lastValue.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(String display) {
+            if (combo.isEditable())
+                return combo.getEditor().getItem().toString();
+            else
+                return display;
+
+        }
+    }
+    public static class MultiSelect extends ComboMultiSelect {
+
+        public long rows = -1;
+        protected ConcatenatingJList list;
+
+        @Override
+        protected void addToPanelAnchor(JPanel p, String def) {
+            list = new ConcatenatingJList(delimiter, lhm.values().toArray());
+            component = list;
+            ListCellRenderer 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 != -1) {
+                double height = renderer.getListCellRendererComponent(list,
+                        new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * 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 {
+        private String delimiter;
+        public ConcatenatingJList(String del, Object[] o) {
+            super(o);
+            delimiter = del;
+        }
+
+        public void setSelectedItem(Object o) {
+            if (o == null) {
+                clearSelection();
+            } else {
+                String s = o.toString();
+                TreeSet<String> parts = new TreeSet<String>(Arrays.asList(s.split(delimiter)));
+                ListModel lm = getModel();
+                int[] intParts = new int[lm.getSize()];
+                int j = 0;
+                for (int i = 0; i < lm.getSize(); i++) {
+                    if (parts.contains((((PresetListEntry)lm.getElementAt(i)).value))) {
+                        intParts[j++]=i;
+                    }
+                }
+                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(Utils.join(delimiter, parts).equals(getSelectedItem()));
+            }
+        }
+
+        public String getSelectedItem() {
+            ListModel 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(((PresetListEntry)lm.getElementAt(si[i])).value);
+            }
+            return builder.toString();
+        }
+    }
+    static public EnumSet<TaggingPresetType> getType(String types) throws SAXException {
+        if (typeCache.containsKey(types))
+            return typeCache.get(types);
+        EnumSet<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));
+            }
+        }
+        typeCache.put(types, result);
+        return result;
+    }
+    
+    static String fixPresetString(String s) {
+        return s == null ? s : s.replaceAll("'","''");
+    }
+    
+    /**
+     * allow escaped comma in comma separated list:
+     * "A\, B\, C,one\, two" --> ["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<String>();
+        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<String>();
+        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<String>();
+        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();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetMenu.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetMenu.java	(revision 6068)
@@ -22,4 +22,5 @@
 public class TaggingPresetMenu extends TaggingPreset {
     public JMenu menu = null; // set by TaggingPresetPreferences
+    @Override
     public void setDisplayName() {
         putValue(Action.NAME, getName());
@@ -30,4 +31,5 @@
         putValue("toolbar", "tagginggroup_" + getRawName());
     }
+    @Override
     public void setIcon(String iconName) {
         super.setIcon(iconName);
@@ -55,4 +57,5 @@
     }
 
+    @Override
     public void actionPerformed(ActionEvent e) {
         Object s = e.getSource();
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java	(revision 6068)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java	(revision 6068)
@@ -0,0 +1,192 @@
+package org.openstreetmap.josm.gui.tagging;
+
+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.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.JOptionPane;
+import org.xml.sax.SAXException;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.SourceEntry;
+import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
+import org.openstreetmap.josm.io.MirroredInputStream;
+import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.tools.XmlObjectParser;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+
+public final class TaggingPresetReader {
+
+    public TaggingPresetReader() {
+    }
+    
+    private static File zipIcons = null;
+    
+    public static LinkedList<String> getPresetSources() {
+        LinkedList<String> sources = new LinkedList<String>();
+
+        for (SourceEntry e : (new TaggingPresetPreference.PresetPrefHelper()).get()) {
+            sources.add(e.url);
+        }
+
+        return sources;
+    }
+
+    
+    public static List<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
+        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.mapOnStart("optional", TaggingPresetItems.Optional.class);
+        parser.mapOnStart("roles", TaggingPresetItems.Roles.class);
+        parser.map("role", TaggingPresetItems.Role.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);
+        
+        LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
+        TaggingPresetMenu lastmenu = null;
+        TaggingPresetItems.Roles lastrole = null;
+        List<TaggingPresetItems.PresetListEntry> listEntries = new LinkedList<TaggingPresetItems.PresetListEntry>();
+
+        if (validate) {
+            parser.startWithValidation(in, "http://josm.openstreetmap.de/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
+        } else {
+            parser.start(in);
+        }
+        while(parser.hasNext()) {
+            Object o = parser.next();
+            if (o instanceof TaggingPresetMenu) {
+                TaggingPresetMenu tp = (TaggingPresetMenu) o;
+                if(tp == lastmenu) {
+                    lastmenu = tp.group;
+                } else
+                {
+                    tp.group = lastmenu;
+                    tp.setDisplayName();
+                    lastmenu = tp;
+                    all.add(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.size() != 0) {
+                    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.PresetListEntry) {
+                        listEntries.add((TaggingPresetItems.PresetListEntry) o);
+                    } else {
+                        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<TaggingPresetItems.PresetListEntry>();
+                        lastrole = null;
+                    }
+                } else
+                    throw new SAXException(tr("Preset sub element without parent"));
+            }
+        }
+        return all;
+    }
+    
+    public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
+        Collection<TaggingPreset> tp;
+        MirroredInputStream s = new MirroredInputStream(source);
+        try {
+            InputStream zip = s.getZipEntry("xml","preset");
+            if(zip != null) {
+                zipIcons = s.getFile();
+            }
+            InputStreamReader r;
+            try {
+                r = new InputStreamReader(zip == null ? s : zip, "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                r = new InputStreamReader(zip == null ? s: zip);
+            }
+            try {
+                tp = readAll(new BufferedReader(r), validate);
+            } finally {
+                Utils.close(r);
+            }
+        } finally {
+            Utils.close(s);
+        }
+        return tp;
+    }
+
+    public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
+        LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
+        for(String source : sources)  {
+            try {
+                allPresets.addAll(readAll(source, validate));
+            } catch (IOException e) {
+                System.err.println(e.getClass().getName()+": "+e.getMessage());
+                System.err.println(source);
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        tr("Could not read tagging preset source: {0}",source),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE
+                        );
+            } catch (SAXException e) {
+                System.err.println(e.getClass().getName()+": "+e.getMessage());
+                System.err.println(source);
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        tr("Error parsing {0}: ", source)+e.getMessage(),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE
+                        );
+            }
+        }
+        return allPresets;
+    }
+    
+    public static Collection<TaggingPreset> readFromPreferences(boolean validate) {
+        return readAll(getPresetSources(), validate);
+    }
+    
+    public static File getZipIcons() {
+        return zipIcons;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java	(revision 6068)
@@ -4,184 +4,15 @@
 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.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.List;
-
-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.JScrollPane;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
+import java.awt.event.ActionListener;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.Item;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.Key;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.Role;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.Roles;
-import org.openstreetmap.josm.gui.widgets.JosmTextField;
 
 
-public class TaggingPresetSearchDialog extends ExtendedDialog implements SelectionChangedListener {
+public class TaggingPresetSearchDialog extends ExtendedDialog {
 
-    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 static class ResultListCellRenderer extends DefaultListCellRenderer {
-        @Override
-        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
-                boolean cellHasFocus) {
-            JLabel result = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-            TaggingPreset tp = (TaggingPreset)value;
-            result.setText(tp.getName());
-            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
-            return result;
-        }
-    }
-
-    private static class ResultListModel extends AbstractListModel {
-
-        private List<PresetClasification> presets = new ArrayList<PresetClasification>();
-
-        public void setPresets(List<PresetClasification> presets) {
-            this.presets = presets;
-            fireContentsChanged(this, 0, Integer.MAX_VALUE);
-        }
-
-        public List<PresetClasification> getPresets() {
-            return presets;
-        }
-
-        @Override
-        public Object getElementAt(int index) {
-            return presets.get(index).preset;
-        }
-
-        @Override
-        public int getSize() {
-            return presets.size();
-        }
-
-    }
-
-    private static class PresetClasification implements Comparable<PresetClasification> {
-        public final TaggingPreset preset;
-        public int classification;
-        public int favoriteIndex;
-        private final Collection<String> groups = new HashSet<String>();
-        private final Collection<String> names = new HashSet<String>();
-        private final Collection<String> tags = new HashSet<String>();
-
-        PresetClasification(TaggingPreset preset) {
-            this.preset = preset;
-            TaggingPreset group = preset.group;
-            while (group != null) {
-                for (String word: group.getLocaleName().toLowerCase().split("\\s")) {
-                    groups.add(word);
-                }
-                group = group.group;
-            }
-            for (String word: preset.getLocaleName().toLowerCase().split("\\s")) {
-                names.add(word);
-            }
-            for (Item item: preset.data) {
-                if (item instanceof TaggingPreset.KeyedItem) {
-                    tags.add(((TaggingPreset.KeyedItem) item).key);
-                    // Should combo values also be added?
-                    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.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(PresetClasification 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.toString();
-        }
-    }
+    private TaggingPresetSelector selector;
 
     private static TaggingPresetSearchDialog instance;
@@ -192,251 +23,23 @@
         return instance;
     }
-
-    private JosmTextField edSearchText;
-    private JList lsResult;
-    private JCheckBox ckOnlyApplicable;
-    private JCheckBox ckSearchInTags;
-    private final EnumSet<PresetType> typesInSelection = EnumSet.noneOf(PresetType.class);
-    private boolean typesInSelectionDirty = true;
-    private final List<PresetClasification> classifications = new ArrayList<PresetClasification>();
-    private ResultListModel lsResultModel = new ResultListModel();
-
     private TaggingPresetSearchDialog() {
         super(Main.parent, tr("Presets"), new String[] {tr("Select"), tr("Cancel")});
-        DataSet.addSelectionListener(this);
-
-        for (TaggingPreset preset: TaggingPresetPreference.taggingPresets) {
-            if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
-                continue;
+        selector = new TaggingPresetSelector();
+        setContent(selector);
+        DataSet.addSelectionListener(selector);
+        selector.setDblClickListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                buttonAction(0, null);
             }
-
-            classifications.add(new PresetClasification(preset));
-        }
-
-        build();
-        filterPresets();
-    }
-
-    @Override
-    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
-        typesInSelectionDirty = true;
+        });
     }
 
     @Override
     public ExtendedDialog showDialog() {
-
-        ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
-        ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
-        edSearchText.setText("");
-        filterPresets();
-
+        selector.init();
         super.showDialog();
-        lsResult.getSelectionModel().clearSelection();
+        selector.clearSelection();
         return this;
-    }
-
-    private void build() {
-        JPanel content = new JPanel();
-        content.setLayout(new BorderLayout());
-
-        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;
-                }
-            }
-        });
-        content.add(edSearchText, BorderLayout.NORTH);
-
-        lsResult = new JList();
-        lsResult.setModel(lsResultModel);
-        lsResult.setCellRenderer(new ResultListCellRenderer());
-        lsResult.addMouseListener(new MouseAdapter() {
-            @Override
-            public void mouseClicked(MouseEvent e) {
-                if (e.getClickCount()>1) {
-                    buttonAction(0, null);
-                }
-            }
-        });
-        content.add(new JScrollPane(lsResult), BorderLayout.CENTER);
-
-        JPanel pnChecks = new JPanel();
-        pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
-
-        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();
-            }
-        });
-
-        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);
-
-        content.add(pnChecks, BorderLayout.SOUTH);
-
-        content.setPreferredSize(new Dimension(400, 300));
-        setContent(content);
-    }
-
-    private 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 void filterPresets() {
-        //TODO Save favorites to file
-        String text = edSearchText.getText().toLowerCase();
-
-        String[] groupWords;
-        String[] nameWords;
-
-        if (text.contains("/")) {
-            groupWords = text.substring(0, text.lastIndexOf('/')).split("[\\s/]");
-            nameWords = text.substring(text.indexOf('/') + 1).split("\\s");
-        } else {
-            groupWords = null;
-            nameWords = text.split("\\s");
-        }
-
-        boolean onlyApplicable = ckOnlyApplicable.isSelected();
-        boolean inTags = ckSearchInTags.isSelected();
-
-        List<PresetClasification> result = new ArrayList<PresetClasification>();
-        PRESET_LOOP:
-            for (PresetClasification presetClasification: classifications) {
-                TaggingPreset preset = presetClasification.preset;
-                presetClasification.classification = 0;
-
-                if (onlyApplicable && preset.types != null) {
-                    boolean found = false;
-                    for (PresetType type: preset.types) {
-                        if (getTypesInSelection().contains(type)) {
-                            found = true;
-                            break;
-                        }
-                    }
-                    if (!found) {
-                        continue;
-                    }
-                }
-
-
-
-                if (groupWords != null && presetClasification.isMatchingGroup(groupWords) == 0) {
-                    continue PRESET_LOOP;
-                }
-
-                int matchName = presetClasification.isMatchingName(nameWords);
-
-                if (matchName == 0) {
-                    if (groupWords == null) {
-                        int groupMatch = presetClasification.isMatchingGroup(nameWords);
-                        if (groupMatch > 0) {
-                            presetClasification.classification = CLASSIFICATION_GROUP_MATCH + groupMatch;
-                        }
-                    }
-                    if (presetClasification.classification == 0 && inTags) {
-                        int tagsMatch = presetClasification.isMatchingTags(nameWords);
-                        if (tagsMatch > 0) {
-                            presetClasification.classification = CLASSIFICATION_TAGS_MATCH + tagsMatch;
-                        }
-                    }
-                } else {
-                    presetClasification.classification = CLASSIFICATION_NAME_MATCH + matchName;
-                }
-
-                if (presetClasification.classification > 0) {
-                    presetClasification.classification += presetClasification.favoriteIndex;
-                    result.add(presetClasification);
-                }
-            }
-
-        Collections.sort(result);
-        lsResultModel.setPresets(result);
-        if (!buttons.isEmpty()) {
-            buttons.get(0).setEnabled(!result.isEmpty());
-        }
-    }
-
-    private EnumSet<PresetType> getTypesInSelection() {
-        if (typesInSelectionDirty) {
-            synchronized (typesInSelection) {
-                typesInSelectionDirty = false;
-                typesInSelection.clear();
-                for (OsmPrimitive primitive : Main.main.getCurrentDataSet().getSelected()) {
-                    if (primitive instanceof Node) {
-                        typesInSelection.add(PresetType.NODE);
-                    } else if (primitive instanceof Way) {
-                        typesInSelection.add(PresetType.WAY);
-                        if (((Way) primitive).isClosed()) {
-                            typesInSelection.add(PresetType.CLOSEDWAY);
-                        }
-                    } else if (primitive instanceof Relation) {
-                        typesInSelection.add(PresetType.RELATION);
-                    }
-                }
-            }
-        }
-        return typesInSelection;
     }
 
@@ -445,24 +48,8 @@
         super.buttonAction(buttonIndex, evt);
         if (buttonIndex == 0) {
-            int selectPreset = lsResult.getSelectedIndex();
-            if (selectPreset == -1) {
-                selectPreset = 0;
-            }
-            TaggingPreset preset = lsResultModel.getPresets().get(selectPreset).preset;
-            for (PresetClasification pc: classifications) {
-                if (pc.preset == preset) {
-                    pc.favoriteIndex = CLASSIFICATION_IN_FAVORITES;
-                } else if (pc.favoriteIndex > 0) {
-                    pc.favoriteIndex--;
-                }
-            }
+            TaggingPreset preset = selector.getSelectedPreset();
             preset.actionPerformed(null);
         }
-
-        SEARCH_IN_TAGS.put(ckSearchInTags.isSelected());
-        if (ckOnlyApplicable.isEnabled()) {
-            ONLY_APPLICABLE.put(ckOnlyApplicable.isSelected());
-        }
+        selector.savePreferences();
     }
-
 }
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java	(revision 6068)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java	(revision 6068)
@@ -0,0 +1,471 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.openstreetmap.josm.gui.tagging;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+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.List;
+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.JScrollPane;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
+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 static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * GUI component to select tagging preset: the list with filter and two checkboxes
+ * @since 6067
+ */
+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 JosmTextField edSearchText;
+    private JList lsResult;
+    private JCheckBox ckOnlyApplicable;
+    private JCheckBox ckSearchInTags;
+    private final EnumSet<TaggingPresetType> typesInSelection = EnumSet.noneOf(TaggingPresetType.class);
+    private boolean typesInSelectionDirty = true;
+    private final List<PresetClassification> classifications = new ArrayList<PresetClassification>();
+    private ResultListModel lsResultModel = new ResultListModel();
+    
+    private ActionListener dblClickListener;
+    private ActionListener clickListener;
+
+    private static class ResultListCellRenderer extends DefaultListCellRenderer {
+        @Override
+        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+                boolean cellHasFocus) {
+            JLabel result = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+            TaggingPreset tp = (TaggingPreset)value;
+            result.setText(tp.getName());
+            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
+            return result;
+        }
+    }
+
+    private static class ResultListModel extends AbstractListModel {
+
+        private List<PresetClassification> presets = new ArrayList<PresetClassification>();
+
+        public void setPresets(List<PresetClassification> presets) {
+            this.presets = presets;
+            fireContentsChanged(this, 0, Integer.MAX_VALUE);
+        }
+
+        public List<PresetClassification> getPresets() {
+            return presets;
+        }
+
+        @Override
+        public Object getElementAt(int index) {
+            return presets.get(index).preset;
+        }
+
+        @Override
+        public int getSize() {
+            return presets.size();
+        }
+
+    }
+
+    private static class PresetClassification implements Comparable<PresetClassification> {
+        public final TaggingPreset preset;
+        public int classification;
+        public int favoriteIndex;
+        private final Collection<String> groups = new HashSet<String>();
+        private final Collection<String> names = new HashSet<String>();
+        private final Collection<String> tags = new HashSet<String>();
+
+        PresetClassification(TaggingPreset preset) {
+            this.preset = preset;
+            TaggingPreset group = preset.group;
+            while (group != null) {
+                for (String word: group.getLocaleName().toLowerCase().split("\\s")) {
+                    groups.add(word);
+                }
+                group = group.group;
+            }
+            for (String word: preset.getLocaleName().toLowerCase().split("\\s")) {
+                names.add(word);
+            }
+            for (TaggingPresetItem item: preset.data) {
+                if (item instanceof KeyedItem) {
+                    tags.add(((KeyedItem) item).key);
+                    // Should combo values also be added?
+                    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.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.toString();
+        }
+    }
+
+    public TaggingPresetSelector() {
+        super(new BorderLayout());
+        
+        loadPresets(TaggingPresetPreference.taggingPresets);
+        
+        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();
+        lsResult.setModel(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));
+
+        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();
+            }
+        });
+
+        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);
+
+        add(pnChecks, BorderLayout.SOUTH);
+
+        setPreferredSize(new Dimension(400, 300));
+        
+        filterPresets();
+    }
+    
+    private 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 void filterPresets() {
+        //TODO Save favorites to file
+        String text = edSearchText.getText().toLowerCase();
+
+        String[] groupWords;
+        String[] nameWords;
+
+        if (text.contains("/")) {
+            groupWords = text.substring(0, text.lastIndexOf('/')).split("[\\s/]");
+            nameWords = text.substring(text.indexOf('/') + 1).split("\\s");
+        } else {
+            groupWords = null;
+            nameWords = text.split("\\s");
+        }
+
+        boolean onlyApplicable = ckOnlyApplicable.isSelected();
+        boolean inTags = ckSearchInTags.isSelected();
+
+        List<PresetClassification> result = new ArrayList<PresetClassification>();
+        PRESET_LOOP:
+            for (PresetClassification presetClasification: classifications) {
+                TaggingPreset preset = presetClasification.preset;
+                presetClasification.classification = 0;
+
+                if (onlyApplicable && preset.types != null) {
+                    boolean found = false;
+                    for (TaggingPresetType type: preset.types) {
+                        if (getTypesInSelection().contains(type)) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        continue;
+                    }
+                }
+
+                if (groupWords != null && presetClasification.isMatchingGroup(groupWords) == 0) {
+                    continue PRESET_LOOP;
+                }
+
+                int matchName = presetClasification.isMatchingName(nameWords);
+
+                if (matchName == 0) {
+                    if (groupWords == null) {
+                        int groupMatch = presetClasification.isMatchingGroup(nameWords);
+                        if (groupMatch > 0) {
+                            presetClasification.classification = CLASSIFICATION_GROUP_MATCH + groupMatch;
+                        }
+                    }
+                    if (presetClasification.classification == 0 && inTags) {
+                        int tagsMatch = presetClasification.isMatchingTags(nameWords);
+                        if (tagsMatch > 0) {
+                            presetClasification.classification = CLASSIFICATION_TAGS_MATCH + tagsMatch;
+                        }
+                    }
+                } else {
+                    presetClasification.classification = CLASSIFICATION_NAME_MATCH + matchName;
+                }
+
+                if (presetClasification.classification > 0) {
+                    presetClasification.classification += presetClasification.favoriteIndex;
+                    result.add(presetClasification);
+                }
+            }
+
+        Collections.sort(result);
+        lsResultModel.setPresets(result);
+
+    }
+    
+    private EnumSet<TaggingPresetType> getTypesInSelection() {
+        if (typesInSelectionDirty) {
+            synchronized (typesInSelection) {
+                typesInSelectionDirty = false;
+                typesInSelection.clear();
+                if (Main.main.getCurrentDataSet() == null) return typesInSelection;
+                for (OsmPrimitive primitive : Main.main.getCurrentDataSet().getSelected()) {
+                    if (primitive instanceof Node) {
+                        typesInSelection.add(TaggingPresetType.NODE);
+                    } else if (primitive instanceof Way) {
+                        typesInSelection.add(TaggingPresetType.WAY);
+                        if (((Way) primitive).isClosed()) {
+                            typesInSelection.add(TaggingPresetType.CLOSEDWAY);
+                        }
+                    } else if (primitive instanceof Relation) {
+                        typesInSelection.add(TaggingPresetType.RELATION);
+                    }
+                }
+            }
+        }
+        return typesInSelection;
+    }
+    
+    @Override
+    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+        typesInSelectionDirty = true;
+    }
+
+    public void init() {
+        ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
+        ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
+        edSearchText.setText("");
+        filterPresets();
+    }
+    
+    public void init(Collection<TaggingPreset> presets) {
+        loadPresets(presets);
+        init();
+    }
+
+    
+    public void clearSelection() {
+        lsResult.getSelectionModel().clearSelection();
+    }
+    
+    /**
+     * Save checkbox values in preferences for future reuse
+     */
+    public void savePreferences() {
+        SEARCH_IN_TAGS.put(ckSearchInTags.isSelected());
+        if (ckOnlyApplicable.isEnabled()) {
+            ONLY_APPLICABLE.put(ckOnlyApplicable.isSelected());
+        }
+    }
+    
+    /**
+     * Determines, which preset is selected at the current moment
+     * @return selected preset (as action)
+     */
+    public TaggingPreset getSelectedPreset() {
+        int idx = lsResult.getSelectedIndex();
+        if (idx == -1) {
+            idx = 0;
+        }
+        TaggingPreset preset = lsResultModel.getPresets().get(idx).preset;
+        for (PresetClassification pc: classifications) {
+            if (pc.preset == preset) {
+                pc.favoriteIndex = CLASSIFICATION_IN_FAVORITES;
+            } else if (pc.favoriteIndex > 0) {
+                pc.favoriteIndex--;
+            }
+        }
+        return preset;
+    }
+
+    private void loadPresets(Collection<TaggingPreset> presets) {
+        for (TaggingPreset preset: presets) {
+            if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
+                continue;
+            }
+            classifications.add(new PresetClassification(preset));
+        }
+    }
+
+    public void setSelectedPreset(TaggingPreset p) {
+        lsResult.setSelectedValue(p, true);
+    }
+    
+    public int getItemCount() {
+        return lsResultModel.getSize();
+    }
+    
+    public void setDblClickListener(ActionListener dblClickListener) {
+        this.dblClickListener = dblClickListener;
+    }
+    
+    public void setClickListener(ActionListener сlickListener) {
+        this.clickListener = сlickListener;
+    }
+    
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSeparator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSeparator.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSeparator.java	(revision 6068)
@@ -3,4 +3,5 @@
 
 public class TaggingPresetSeparator extends TaggingPreset {
+    @Override
     public void setDisplayName() {}
 }
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetType.java	(revision 6068)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetType.java	(revision 6068)
@@ -0,0 +1,50 @@
+// 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
+ */
+public enum TaggingPresetType {
+    NODE("Mf_node", "node"), WAY("Mf_way", "way"), RELATION("Mf_relation", "relation"), CLOSEDWAY("Mf_closedway", "closedway");
+    private final String iconName;
+    private final String name;
+
+    TaggingPresetType(String iconName, String name) {
+        this.iconName = iconName;
+        this.name = name;
+    }
+
+    public String getIconName() {
+        return iconName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static TaggingPresetType forPrimitive(OsmPrimitive p) {
+        return forPrimitiveType(p.getDisplayType());
+    }
+
+    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);
+    }
+
+    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/ac/AutoCompletionManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 6068)
@@ -26,4 +26,6 @@
 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.tools.MultiMap;
 
@@ -147,7 +149,7 @@
     public static void cachePresets(Collection<TaggingPreset> presets) {
         for (final TaggingPreset p : presets) {
-            for (TaggingPreset.Item item : p.data) {
-                if (item instanceof TaggingPreset.KeyedItem) {
-                    TaggingPreset.KeyedItem ki = (TaggingPreset.KeyedItem) item;
+            for (TaggingPresetItem item : p.data) {
+                if (item instanceof TaggingPresetItems.KeyedItem) {
+                    TaggingPresetItems.KeyedItem ki = (TaggingPresetItems.KeyedItem) item;
                     if (ki.key != null && ki.getValues() != null) {
                         try {
@@ -157,7 +159,7 @@
                         }
                     }
-                } else if (item instanceof TaggingPreset.Roles) {
-                    TaggingPreset.Roles r = (TaggingPreset.Roles) item;
-                    for (TaggingPreset.Role i : r.roles) {
+                } else if (item instanceof TaggingPresetItems.Roles) {
+                    TaggingPresetItems.Roles r = (TaggingPresetItems.Roles) item;
+                    for (TaggingPresetItems.Role i : r.roles) {
                         if (i.key != null) {
                             presetRoleCache.add(i.key);
@@ -288,4 +290,5 @@
      **/
 
+    @Override
     public void primitivesAdded(PrimitivesAddedEvent event) {
         if (dirty)
@@ -294,8 +297,10 @@
     }
 
+    @Override
     public void primitivesRemoved(PrimitivesRemovedEvent event) {
         dirty = true;
     }
 
+    @Override
     public void tagsChanged(TagsChangedEvent event) {
         if (dirty)
@@ -319,14 +324,19 @@
     }
 
+    @Override
     public void nodeMoved(NodeMovedEvent event) {/* ignored */}
 
+    @Override
     public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
 
+    @Override
     public void relationMembersChanged(RelationMembersChangedEvent event) {
         dirty = true; // TODO: not necessary to rebuid if a member is added
     }
 
+    @Override
     public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignored */}
 
+    @Override
     public void dataChanged(DataChangedEvent event) {
         dirty = true;
Index: trunk/src/org/openstreetmap/josm/tools/TaggingPresetNameTemplateList.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/TaggingPresetNameTemplateList.java	(revision 6067)
+++ trunk/src/org/openstreetmap/josm/tools/TaggingPresetNameTemplateList.java	(revision 6068)
@@ -10,5 +10,5 @@
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
+import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
 
 /**
@@ -40,5 +40,5 @@
 
         for (TaggingPreset t : presetsWithPattern) {
-            Collection<PresetType> type = Collections.singleton(PresetType.forPrimitive(primitive));
+            Collection<TaggingPresetType> type = Collections.singleton(TaggingPresetType.forPrimitive(primitive));
             if (t.typeMatches(type)) {
                 if (t.nameTemplateFilter != null) {
