Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 4633)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 4634)
@@ -65,5 +65,7 @@
  * accessed using one of the get...() methods. You can use the same preference
  * key in different parts of the code, but the default value must be the same
- * everywhere. null is a legitimate default value.
+ * everywhere. A default value of null means, the setting has been requested, but
+ * no default value was set. This is used in advanced preferences to present a list
+ * off all possible settings.
  *
  * At the moment, there is no such thing as an empty value.
@@ -99,4 +101,5 @@
         T getValue();
         void visit(SettingVisitor visitor);
+        Setting<T> getNullInstance();
     }
 
@@ -118,4 +121,7 @@
             visitor.visit(this);
         }
+        public StringSetting getNullInstance() {
+            return new StringSetting(null);
+        }
     }
 
@@ -127,4 +133,7 @@
             visitor.visit(this);
         }
+        public ListSetting getNullInstance() {
+            return new ListSetting(null);
+        }
     }
 
@@ -136,4 +145,7 @@
             visitor.visit(this);
         }
+        public ListListSetting getNullInstance() {
+            return new ListListSetting(null);
+        }
     }
 
@@ -144,4 +156,7 @@
         public void visit(SettingVisitor visitor) {
             visitor.visit(this);
+        }
+        public MapListSetting getNullInstance() {
+            return new MapListSetting(null);
         }
     }
@@ -825,7 +840,5 @@
      */
     public Collection<String> getCollection(String key, Collection<String> def) {
-        if (def != null) {
-            putCollectionDefault(key, new ArrayList<String>(def));
-        }
+        putCollectionDefault(key, def == null ? null : new ArrayList<String>(def));
         Collection<String> prop = getCollectionInternal(key);
         if (prop != null)
@@ -904,5 +917,5 @@
     }
 
-    private static boolean equalCollection(Collection<String> a, Collection<String> b) {
+    public static boolean equalCollection(Collection<String> a, Collection<String> b) {
         if (a == null) return b == null;
         if (b == null) return false;
@@ -947,4 +960,6 @@
             }
             putArrayDefault(key, Collections.unmodifiableList(defCopy));
+        } else {
+            putArrayDefault(key, null);
         }
         List<List<String>> prop = getArrayInternal(key);
@@ -955,4 +970,15 @@
         } else
             return def;
+    }
+
+    public Collection<Collection<String>> getArray(String key) {
+        putArrayDefault(key, null);
+        List<List<String>> prop = getArrayInternal(key);
+        if (prop != null) {
+            @SuppressWarnings("unchecked")
+            Collection<Collection<String>> prop_cast = (Collection) prop;
+            return prop_cast;
+        } else
+            return Collections.emptyList();
     }
 
@@ -1020,5 +1046,5 @@
     }
 
-    private static boolean equalArray(Collection<Collection<String>> a, Collection<List<String>> b) {
+    public static boolean equalArray(Collection<Collection<String>> a, Collection<List<String>> b) {
         if (a == null) return b == null;
         if (b == null) return false;
@@ -1043,4 +1069,6 @@
             }
             putListOfStructsDefault(key, Collections.unmodifiableList(defCopy));
+        } else {
+            putListOfStructsDefault(key, null);
         }
         Collection<Map<String, String>> prop = getListOfStructsInternal(key);
@@ -1117,5 +1145,5 @@
     }
 
-    private static boolean equalListOfStructs(Collection<Map<String, String>> a, Collection<Map<String, String>> b) {
+    public static boolean equalListOfStructs(Collection<Map<String, String>> a, Collection<Map<String, String>> b) {
         if (a == null) return b == null;
         if (b == null) return false;
@@ -1298,4 +1326,62 @@
         }
         return struct;
+    }
+
+    public boolean putSetting(final String key, Setting value) {
+        if (value == null) return false;
+        class PutVisitor implements SettingVisitor {
+            public boolean changed;
+            public void visit(StringSetting setting) {
+                changed = put(key, setting.getValue());
+            }
+            public void visit(ListSetting setting) {
+                changed = putCollection(key, setting.getValue());
+            }
+            public void visit(ListListSetting setting) {
+                changed = putArray(key, (Collection) setting.getValue());
+            }
+            public void visit(MapListSetting setting) {
+                changed = putListOfStructs(key, setting.getValue());
+            }
+        };
+        PutVisitor putVisitor = new PutVisitor();
+        value.visit(putVisitor);
+        return putVisitor.changed;
+    }
+
+    public Map<String, Setting> getAllSettings() {
+        Map<String, Setting> settings = new TreeMap<String, Setting>();
+
+        for (Entry<String, String> e : properties.entrySet()) {
+            settings.put(e.getKey(), new StringSetting(e.getValue()));
+        }
+        for (Entry<String, List<String>> e : collectionProperties.entrySet()) {
+            settings.put(e.getKey(), new ListSetting(e.getValue()));
+        }
+        for (Entry<String, List<List<String>>> e : arrayProperties.entrySet()) {
+            settings.put(e.getKey(), new ListListSetting(e.getValue()));
+        }
+        for (Entry<String, List<Map<String, String>>> e : listOfStructsProperties.entrySet()) {
+            settings.put(e.getKey(), new MapListSetting(e.getValue()));
+        }
+        return settings;
+    }
+
+    public Map<String, Setting> getAllDefaults() {
+        Map<String, Setting> allDefaults = new TreeMap<String, Setting>();
+
+        for (Entry<String, String> e : defaults.entrySet()) {
+            allDefaults.put(e.getKey(), new StringSetting(e.getValue()));
+        }
+        for (Entry<String, List<String>> e : collectionDefaults.entrySet()) {
+            allDefaults.put(e.getKey(), new ListSetting(e.getValue()));
+        }
+        for (Entry<String, List<List<String>>> e : arrayDefaults.entrySet()) {
+            allDefaults.put(e.getKey(), new ListListSetting(e.getValue()));
+        }
+        for (Entry<String, List<Map<String, String>>> e : listOfStructsDefaults.entrySet()) {
+            allDefaults.put(e.getKey(), new MapListSetting(e.getValue()));
+        }
+        return allDefaults;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/preferences/AdvancedPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/AdvancedPreference.java	(revision 4633)
+++ 	(revision )
@@ -1,347 +1,0 @@
-// License: GPL. Copyright 2007 by Immanuel Scholz and others
-package org.openstreetmap.josm.gui.preferences;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.Map.Entry;
-
-import javax.swing.Box;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.JTextField;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.table.DefaultTableCellRenderer;
-import javax.swing.table.DefaultTableModel;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.tools.GBC;
-
-public class AdvancedPreference implements PreferenceSetting {
-
-    public static class Factory implements PreferenceSettingFactory {
-        public PreferenceSetting createPreferenceSetting() {
-            return new AdvancedPreference();
-        }
-    }
-
-    private Map<String,String> orig;
-    private Map<String,String> defaults;
-    private DefaultTableModel model;
-    protected Map<String, String> data;
-    protected JTextField txtFilter;
-
-    public void addGui(final PreferenceTabbedPane gui) {
-        JPanel p = gui.createPreferenceTab("advanced", tr("Advanced Preferences"),
-                tr("Setting Preference entries directly. Use with caution!"), false);
-
-        txtFilter = new JTextField();
-        JLabel lbFilter = new JLabel(tr("Search: "));
-        lbFilter.setLabelFor(txtFilter);
-        p.add(lbFilter);
-        p.add(txtFilter, GBC.eol().fill(GBC.HORIZONTAL));
-        txtFilter.getDocument().addDocumentListener(new DocumentListener(){
-            public void changedUpdate(DocumentEvent e) {
-                action();
-            }
-
-            public void insertUpdate(DocumentEvent e) {
-                action();
-            }
-
-            public void removeUpdate(DocumentEvent e) {
-                action();
-            }
-
-            private void action() {
-                dataToModel();
-            }
-        });
-
-        model = new DefaultTableModel(new String[]{tr("Key"), tr("Value")},0) {
-            @Override public boolean isCellEditable(int row, int column) {
-                return column != 0;
-            }
-            @Override public void fireTableCellUpdated(int row, int column)
-            {
-                super.fireTableCellUpdated(row, column);
-                if(column == 1)
-                {
-                    data.put((String) model.getValueAt(row, 0),
-                            (String) model.getValueAt(row, 1));
-                }
-            }
-
-        };
-        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(){
-            @Override
-            public Component getTableCellRendererComponent(JTable table, Object value,
-                    boolean isSelected, boolean hasFocus, int row, int column)
-            {
-                JLabel label=new JLabel();
-                String s = defaults.get(value);
-                if(s != null)
-                {
-                    if(s.equals(model.getValueAt(row, 1))) {
-                        label.setToolTipText(tr("Current value is default."));
-                    } else {
-                        label.setToolTipText(tr("Default value is ''{0}''.", s));
-                    }
-                } else {
-                    label.setToolTipText(tr("Default value currently unknown (setting has not been used yet)."));
-                }
-                label.setText((String)value);
-                return label;
-            }
-        };
-        final JTable list = new JTable(model);
-        list.putClientProperty("terminateEditOnFocusLost", true);
-        list.getColumn(tr("Key")).setCellRenderer(renderer);
-        JScrollPane scroll = new JScrollPane(list);
-        p.add(scroll, GBC.eol().fill(GBC.BOTH));
-        scroll.setPreferredSize(new Dimension(400,200));
-
-        orig = Main.pref.getAllPrefix("");
-        defaults = Main.pref.getDefaults();
-        orig.remove("osm-server.password");
-        defaults.remove("osm-server.password");
-        prepareData();
-        dataToModel();
-
-        JButton add = new JButton(tr("Add"));
-        p.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
-        p.add(add, GBC.std().insets(0,5,0,0));
-        add.addActionListener(new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                addPreference(gui);
-            }
-        });
-
-        JButton edit = new JButton(tr("Edit"));
-        p.add(edit, GBC.std().insets(5,5,5,0));
-        edit.addActionListener(new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                editPreference(gui, list);
-            }
-        });
-
-        JButton delete = new JButton(tr("Delete"));
-        p.add(delete, GBC.std().insets(0,5,0,0));
-        delete.addActionListener(new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                removePreference(gui, list);
-            }
-        });
-
-        list.addMouseListener(new MouseAdapter(){
-            @Override public void mouseClicked(MouseEvent e) {
-                if (e.getClickCount() == 2) {
-                    editPreference(gui, list);
-                }
-            }
-        });
-    }
-
-    private void prepareData() {
-        TreeSet<String> ts = new TreeSet<String>(orig.keySet());
-        for (String s : defaults.keySet())
-        {
-            if(!ts.contains(s)) {
-                ts.add(s);
-            }
-        }
-        data = new TreeMap<String, String>();
-        for (String s : ts)
-        {
-            String val = Main.pref.get(s);
-            if(val == null) {
-                val = "";
-            }
-            data.put(s, val);
-        }
-    }
-
-    private void dataToModel() {
-        while (model.getRowCount() > 0) {
-            model.removeRow(0);
-        }
-        for (String prefKey : data.keySet()) {
-            String prefValue = data.get(prefKey);
-            String input[] = txtFilter.getText().split("\\s+");
-            boolean canHas = true;
-
-            // Make 'wmsplugin cache' search for e.g. 'cache.wmsplugin'
-            final String prefKeyLower = prefKey.toLowerCase();
-            final String prefValueLower = prefValue.toLowerCase();
-            for (String bit : input) {
-                bit = bit.toLowerCase();
-                if (!prefKeyLower.contains(bit) && !prefValueLower.contains(bit)) {
-                    canHas = false;
-                }
-            }
-
-            if (canHas) {
-                model.addRow(new String[] {prefKey, prefValue});
-            }
-        }
-    }
-
-    public boolean ok() {
-        for (String key : data.keySet()) {
-            String value = data.get(key);
-            if(value.length() != 0)
-            {
-                String origValue = orig.get(key);
-                if (origValue == null || !origValue.equals(value)) {
-                    Main.pref.put(key, value);
-                }
-                orig.remove(key); // processed.
-            }
-        }
-        for (Entry<String, String> e : orig.entrySet()) {
-            Main.pref.put(e.getKey(), null);
-        }
-        return false;
-    }
-
-    private void removePreference(final PreferenceTabbedPane gui, final JTable list) {
-        if (list.getSelectedRowCount() == 0) {
-            JOptionPane.showMessageDialog(
-                    gui,
-                    tr("Please select the row to delete."),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-            return;
-        }
-        for(int row: list.getSelectedRows()) {
-            data.put((String) model.getValueAt(row, 0), "");
-            model.setValueAt("", row, 1);
-        }
-    }
-
-    private void addPreference(final PreferenceTabbedPane gui) {
-        String s[] = showEditDialog(gui, tr("Enter a new key/value pair"),
-            "", "");
-        if(s != null && !s[0].isEmpty() && !s[1].isEmpty()) {
-            data.put(s[0], s[1]);
-            dataToModel();
-        }
-    }
-
-    private void editPreference(final PreferenceTabbedPane gui, final JTable list) {
-        if (list.getSelectedRowCount() != 1) {
-            JOptionPane.showMessageDialog(
-                    gui,
-                    tr("Please select the row to edit."),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-            return;
-        }
-        String key = (String)model.getValueAt(list.getSelectedRow(), 0);
-        String value = data.get(key);
-        if(value.isEmpty())
-            value = defaults.get(key);
-        String s[] = showEditDialog(gui, tr("Change a key/value pair"),
-            key, value);
-        if(s != null && !s[0].isEmpty()) {
-            data.put(s[0], s[1]);
-            if(!s[0].equals(key))
-                data.put(key,"");
-            dataToModel();
-        }
-    }
-
-    private String[] showEditDialog(final PreferenceTabbedPane gui, String title,
-    String key, String value) {
-        JPanel p = new JPanel(new GridBagLayout());
-        p.add(new JLabel(tr("Key")), GBC.std().insets(0,0,5,0));
-        JTextField tkey = new JTextField(key, 50);
-        JTextField tvalue = new JTextField(value, 50);
-        p.add(tkey, GBC.eop().insets(5,0,0,0).fill(GBC.HORIZONTAL));
-        PrefValueTableModel model = new PrefValueTableModel(value);
-        p.add(new JLabel(tr("Values")), GBC.std().insets(0,0,5,0));
-        JTable table = new JTable(model);
-        table.putClientProperty("terminateEditOnFocusLost", true);
-        table.getTableHeader().setVisible(false);
-        JScrollPane pane = new JScrollPane(table);
-        Dimension d = pane.getPreferredSize();
-        d.height = (d.height/20)*(model.getRowCount()+4);
-        pane.setPreferredSize(d);
-        p.add(pane, GBC.eol().insets(5,10,0,0).fill(GBC.HORIZONTAL));
-        ExtendedDialog ed = new ExtendedDialog(gui, title,
-                new String[] {tr("OK"), tr("Cancel")});
-        ed.setButtonIcons(new String[] {"ok.png", "cancel.png"});
-        ed.setContent(p);
-        ed.showDialog();
-        if(ed.getValue() == 1) {
-            return new String[]{tkey.getText(), model.getText()};
-        }
-        return null;
-    }
-    class PrefValueTableModel extends DefaultTableModel {
-        private final ArrayList<String> data = new ArrayList<String>();
-        public PrefValueTableModel(String val) {
-            data.addAll(Arrays.asList(val.split("\u001e")));
-            setColumnIdentifiers(new String[]{""});
-        }
-
-        public String getText() {
-            String s = null;
-            for(String a : data)
-            {
-                if(s == null) {
-                    s = a;
-                } else {
-                    s += "\u001e" + a;
-                }
-            }
-            return s == null ? "" : s;
-        }
-
-        @Override
-        public int getRowCount() {
-            return data == null ? 1 : data.size()+1;
-        }
-
-        @Override
-        public Object getValueAt(int row, int column) {
-            return data.size() == row ? "" : data.get(row);
-        }
-
-        @Override
-        public void setValueAt(Object o, int row, int column) {
-            String s = (String)o;
-            if(row == data.size()) {
-                data.add(s);
-                fireTableRowsInserted(row+1, row+1);
-            } else {
-                data.set(row, s);
-            }
-            fireTableCellUpdated(row, column);
-        }
-
-        @Override
-        public boolean isCellEditable(int row, int column) {
-            return true;
-        }
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java	(revision 4633)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java	(revision 4634)
@@ -28,4 +28,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference;
 import org.openstreetmap.josm.plugins.PluginDownloadTask;
 import org.openstreetmap.josm.plugins.PluginHandler;
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java	(revision 4634)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java	(revision 4634)
@@ -0,0 +1,477 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+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.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.DefaultCellEditor;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.data.Preferences.ListListSetting;
+import org.openstreetmap.josm.data.Preferences.ListSetting;
+import org.openstreetmap.josm.data.Preferences.MapListSetting;
+import org.openstreetmap.josm.data.Preferences.Setting;
+import org.openstreetmap.josm.data.Preferences.StringSetting;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Utils;
+
+public class AdvancedPreference implements PreferenceSetting {
+
+    public static class Factory implements PreferenceSettingFactory {
+        public PreferenceSetting createPreferenceSetting() {
+            return new AdvancedPreference();
+        }
+    }
+
+    public static class PrefEntry implements Comparable<PrefEntry> {
+        private String key;
+        private Setting value;
+        private Setting defaultValue;
+        private boolean isDefault;
+        private boolean changed;
+
+        public PrefEntry(String key, Setting value, Setting defaultValue, boolean isDefault) {
+            CheckParameterUtil.ensureParameterNotNull(key);
+            CheckParameterUtil.ensureParameterNotNull(value);
+            CheckParameterUtil.ensureParameterNotNull(defaultValue);
+            this.key = key;
+            this.value = value;
+            this.defaultValue = defaultValue;
+            this.isDefault = isDefault;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public Setting getValue() {
+            return value;
+        }
+
+        public void setValue(Setting value) {
+            this.value = value;
+            changed = true;
+            isDefault = false;
+        }
+
+        public boolean isDefault() {
+            return isDefault;
+        }
+
+        public boolean isChanged() {
+            return changed;
+        }
+
+        public void reset() {
+            value = defaultValue;
+            changed = true;
+            isDefault = true;
+        }
+
+        public int compareTo(PrefEntry other) {
+            return key.compareTo(other.key);
+        }
+    }
+
+    private AllSettingsTableModel model;
+    protected List<PrefEntry> data;
+    protected List<PrefEntry> displayData;
+    protected JTextField txtFilter;
+
+    public void addGui(final PreferenceTabbedPane gui) {
+        JPanel p = gui.createPreferenceTab("advanced", tr("Advanced Preferences"),
+                tr("Setting Preference entries directly. Use with caution!"), false);
+
+        txtFilter = new JTextField();
+        JLabel lbFilter = new JLabel(tr("Search: "));
+        lbFilter.setLabelFor(txtFilter);
+        p.add(lbFilter);
+        p.add(txtFilter, GBC.eol().fill(GBC.HORIZONTAL));
+        txtFilter.getDocument().addDocumentListener(new DocumentListener(){
+            @Override public void changedUpdate(DocumentEvent e) {
+                action();
+            }
+            @Override public void insertUpdate(DocumentEvent e) {
+                action();
+            }
+            @Override public void removeUpdate(DocumentEvent e) {
+                action();
+            }
+            private void action() {
+                applyFilter();
+            }
+        });
+
+        Map<String, Setting> orig = Main.pref.getAllSettings();
+        Map<String, Setting> defaults = Main.pref.getAllDefaults();
+        orig.remove("osm-server.password");
+        defaults.remove("osm-server.password");
+        prepareData(orig, defaults);
+        model = new AllSettingsTableModel();
+        applyFilter();
+
+        final JTable list = new JTable(model);
+        list.putClientProperty("terminateEditOnFocusLost", true);
+        list.getColumnModel().getColumn(1).setCellRenderer(new SettingCellRenderer());
+        list.getColumnModel().getColumn(1).setCellEditor(new SettingCellEditor());
+
+        JScrollPane scroll = new JScrollPane(list);
+        p.add(scroll, GBC.eol().fill(GBC.BOTH));
+        scroll.setPreferredSize(new Dimension(400,200));
+
+        JButton add = new JButton(tr("Add"));
+        p.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
+        p.add(add, GBC.std().insets(0,5,0,0));
+        add.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                addPreference(gui);
+            }
+        });
+
+        JButton edit = new JButton(tr("Edit"));
+        p.add(edit, GBC.std().insets(5,5,5,0));
+        edit.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                editPreference(gui, list);
+            }
+        });
+
+        JButton reset = new JButton(tr("Reset"));
+        p.add(reset, GBC.std().insets(0,5,0,0));
+        reset.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                resetPreference(gui, list);
+            }
+        });
+
+        list.addMouseListener(new MouseAdapter(){
+            @Override public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2) {
+                    editPreference(gui, list);
+                }
+            }
+        });
+    }
+
+    private void prepareData(Map<String, Setting> orig, Map<String, Setting> defaults) {
+        data = new ArrayList<PrefEntry>();
+        for (Entry<String, Setting> e : orig.entrySet()) {
+            Setting value = e.getValue();
+            Setting def = defaults.get(e.getKey());
+            if (def == null) {
+                def = value.getNullInstance();
+            }
+            PrefEntry en = new PrefEntry(e.getKey(), value, def, false);
+            data.add(en);
+        }
+        for (Entry<String, Setting> e : defaults.entrySet()) {
+            if (!orig.containsKey(e.getKey())) {
+                PrefEntry en = new PrefEntry(e.getKey(), e.getValue(), e.getValue(), true);
+                data.add(en);
+            }
+        }
+        Collections.sort(data);
+        displayData = new ArrayList<PrefEntry>(data);
+    }
+
+    class AllSettingsTableModel extends DefaultTableModel {
+
+        public AllSettingsTableModel() {
+            setColumnIdentifiers(new String[]{tr("Key"), tr("Value")});
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return column == 1 && (displayData.get(row).getValue() instanceof StringSetting);
+        }
+
+        @Override
+        public int getRowCount() {
+            return displayData.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            if (column == 0)
+                return displayData.get(row).getKey();
+            else
+                return displayData.get(row);
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            PrefEntry pe = displayData.get(row);
+            String s = (String) o;
+            if (!s.equals(pe.getValue().getValue())) {
+                pe.setValue(new StringSetting(s));
+                fireTableCellUpdated(row, column);
+            }
+        }
+    }
+
+    private class SettingCellRenderer extends DefaultTableCellRenderer {
+        @Override
+        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+            if (value == null)
+                return this;
+            PrefEntry pe = (PrefEntry) value;
+            Setting setting = pe.getValue();
+            Object val = setting.getValue();
+            String display = val != null ? val.toString() : "<html><i>&lt;"+tr("unset")+"&gt;</i></html>";
+
+            JLabel label = (JLabel)super.getTableCellRendererComponent(table,
+                    display, isSelected, hasFocus, row, column);
+            if (!pe.isDefault()) {
+                label.setFont(label.getFont().deriveFont(Font.BOLD));
+            }
+            //label.setToolTipText("..."); TODO
+            return label;
+        }
+    }
+
+    private class SettingCellEditor extends DefaultCellEditor {
+        public SettingCellEditor() {
+            super(new JTextField());
+        }
+
+        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+            PrefEntry pe = (PrefEntry) value;
+            StringSetting stg = (StringSetting) pe.getValue();
+            String s = stg.getValue() == null ? "" : stg.getValue();
+            return super.getTableCellEditorComponent(table, s, isSelected, row, column);
+        }
+    }
+
+    private void applyFilter() {
+        displayData.clear();
+        for (PrefEntry e : data) {
+
+            String prefKey = e.getKey();
+            Setting valueSetting = e.getValue();
+            String prefValue = valueSetting.getValue() == null ? "" : valueSetting.getValue().toString();
+
+            String input[] = txtFilter.getText().split("\\s+");
+            boolean canHas = true;
+
+            // Make 'wmsplugin cache' search for e.g. 'cache.wmsplugin'
+            final String prefKeyLower = prefKey.toLowerCase();
+            final String prefValueLower = prefValue.toLowerCase();
+            for (String bit : input) {
+                bit = bit.toLowerCase();
+                if (!prefKeyLower.contains(bit) && !prefValueLower.contains(bit)) {
+                    canHas = false;
+                    break;
+                }
+            }
+            if (canHas) {
+                displayData.add(e);
+            }
+        }
+        model.fireTableDataChanged();
+    }
+
+    @Override
+    public boolean ok() {
+        for (PrefEntry e : data) {
+            if (e.isChanged()) {
+                Main.pref.putSetting(e.getKey(), e.getValue());
+            }
+        }
+        return false;
+    }
+
+    private void resetPreference(final PreferenceTabbedPane gui, final JTable list) {
+        if (list.getSelectedRowCount() == 0) {
+            JOptionPane.showMessageDialog(
+                    gui,
+                    tr("Please select the row to delete."),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE
+            );
+            return;
+        }
+        for (int row : list.getSelectedRows()) {
+            PrefEntry e = displayData.get(row);
+            e.reset();
+        }
+        model.fireTableDataChanged();
+    }
+
+    private void addPreference(final PreferenceTabbedPane gui) {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel(tr("Key")), GBC.std().insets(0,0,5,0));
+        JTextField tkey = new JTextField("", 50);
+        p.add(tkey, GBC.eop().insets(5,0,0,0).fill(GBC.HORIZONTAL));
+
+        p.add(new JLabel(tr("Select Setting Type:")), GBC.eol().insets(5,15,5,0));
+
+        JRadioButton rbString = new JRadioButton(tr("Simple"));
+        JRadioButton rbList = new JRadioButton(tr("List"));
+        JRadioButton rbListList = new JRadioButton(tr("List of lists"));
+        JRadioButton rbMapList = new JRadioButton(tr("List of maps"));
+
+        ButtonGroup group = new ButtonGroup();
+        group.add(rbString);
+        group.add(rbList);
+        group.add(rbListList);
+        group.add(rbMapList);
+
+        p.add(rbString, GBC.eol());
+        p.add(rbList, GBC.eol());
+        p.add(rbListList, GBC.eol());
+        p.add(rbMapList, GBC.eol());
+
+        rbString.setSelected(true);
+
+        ExtendedDialog dlg = new ExtendedDialog(gui, tr("Add setting"), new String[] {tr("OK"), tr("Cancel")});
+        dlg.setButtonIcons(new String[] {"ok.png", "cancel.png"});
+        dlg.setContent(p);
+        dlg.showDialog();
+
+        PrefEntry pe = null;
+        boolean ok = false;
+        if (dlg.getValue() == 1) {
+            if (rbString.isSelected()) {
+                StringSetting sSetting = new StringSetting(null);
+                pe = new PrefEntry(tkey.getText(), sSetting, sSetting, false);
+                StringEditor sEditor = new StringEditor(gui, pe, sSetting);
+                sEditor.showDialog();
+                if (sEditor.getValue() == 1) {
+                    String data = sEditor.getData();
+                    if (!Utils.equal(sSetting.getValue(), data)) {
+                        pe.setValue(new StringSetting(data));
+                        ok = true;
+                    }
+                }
+            } else if (rbList.isSelected()) {
+                ListSetting lSetting = new ListSetting(null);
+                pe = new PrefEntry(tkey.getText(), lSetting, lSetting, false);
+                ListEditor lEditor = new ListEditor(gui, pe, lSetting);
+                lEditor.showDialog();
+                if (lEditor.getValue() == 1) {
+                    List<String> data = lEditor.getData();
+                    if (!Preferences.equalCollection(lSetting.getValue(), data)) {
+                        pe.setValue(new ListSetting(data));
+                        ok = true;
+                    }
+                }
+            } else if (rbListList.isSelected()) {
+                ListListSetting llSetting = new ListListSetting(null);
+                pe = new PrefEntry(tkey.getText(), llSetting, llSetting, false);
+                ListListEditor llEditor = new ListListEditor(gui, pe, llSetting);
+                llEditor.showDialog();
+                if (llEditor.getValue() == 1) {
+                    List<List<String>> data = llEditor.getData();
+                    if (!Preferences.equalArray((Collection) llSetting.getValue(), data)) {
+                        pe.setValue(new ListListSetting(data));
+                        ok = true;
+                    }
+                }
+            } else if (rbMapList.isSelected()) {
+                MapListSetting mlSetting = new MapListSetting(null);
+                pe = new PrefEntry(tkey.getText(), mlSetting, mlSetting, false);
+                MapListEditor mlEditor = new MapListEditor(gui, pe, mlSetting);
+                mlEditor.showDialog();
+                if (mlEditor.getValue() == 1) {
+                    List<Map<String, String>> data = mlEditor.getData();
+                    if (!Preferences.equalListOfStructs(mlSetting.getValue(), data)) {
+                        pe.setValue(new MapListSetting(data));
+                        ok = true;
+                    }
+                }
+            }
+            if (ok) {
+                data.add(pe);
+                Collections.sort(data);
+                applyFilter();
+            }
+        }
+    }
+
+    private void editPreference(final PreferenceTabbedPane gui, final JTable list) {
+        if (list.getSelectedRowCount() != 1) {
+            JOptionPane.showMessageDialog(
+                    gui,
+                    tr("Please select the row to edit."),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE
+            );
+            return;
+        }
+        final PrefEntry e = (PrefEntry) model.getValueAt(list.getSelectedRow(), 1);
+        Setting stg = e.getValue();
+        if (stg instanceof StringSetting) {
+            list.editCellAt(list.getSelectedRow(), 1);
+            Component editor = list.getEditorComponent();
+            if (editor != null) {
+                editor.requestFocus();
+            }
+        } else if (stg instanceof ListSetting) {
+            ListSetting lSetting = (ListSetting) stg;
+            ListEditor lEditor = new ListEditor(gui, e, lSetting);
+            lEditor.showDialog();
+            if (lEditor.getValue() == 1) {
+                List<String> data = lEditor.getData();
+                if (!Preferences.equalCollection(lSetting.getValue(), data)) {
+                    e.setValue(new ListSetting(data));
+                    applyFilter();
+                }
+            }
+        } else if (stg instanceof ListListSetting) {
+            ListListEditor llEditor = new ListListEditor(gui, e, (ListListSetting) stg);
+            llEditor.showDialog();
+            if (llEditor.getValue() == 1) {
+                List<List<String>> data = llEditor.getData();
+                if (!Preferences.equalArray((Collection) stg.getValue(), data)) {
+                    e.setValue(new ListListSetting(data));
+                    applyFilter();
+                }
+            }
+        } else if (stg instanceof MapListSetting) {
+            MapListSetting mlSetting = (MapListSetting) stg;
+            MapListEditor mlEditor = new MapListEditor(gui, e, mlSetting);
+            mlEditor.showDialog();
+            if (mlEditor.getValue() == 1) {
+                List<Map<String, String>> data = mlEditor.getData();
+                if (!Preferences.equalListOfStructs(mlSetting.getValue(), data)) {
+                    e.setValue(new MapListSetting(data));
+                    applyFilter();
+                }
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListEditor.java	(revision 4634)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListEditor.java	(revision 4634)
@@ -0,0 +1,105 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.Preferences.ListSetting;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference.PrefEntry;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class ListEditor extends ExtendedDialog {
+
+    List<String> data;
+    PrefEntry entry;
+
+    public ListEditor(final PreferenceTabbedPane gui, PrefEntry entry, ListSetting setting) {
+        super(gui, tr("Change list setting"), new String[] {tr("OK"), tr("Cancel")});
+        this.entry = entry;
+        List<String> orig = setting.getValue();
+        if (orig != null) {
+            data = new ArrayList<String>(orig);
+        } else {
+            data = new ArrayList<String>();
+        }
+        setButtonIcons(new String[] {"ok.png", "cancel.png"});
+        setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(gui, new Dimension(300, 350)));
+        setContent(build(), false);
+    }
+
+    public List<String> getData() {
+        return data;
+    }
+
+    protected JPanel build() {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel(tr("Key: {0}", entry.getKey())), GBC.eol().insets(0,0,5,0));
+        ListSettingTableModel listModel = new ListSettingTableModel();
+        JTable table = new JTable(listModel);
+        table.putClientProperty("terminateEditOnFocusLost", true);
+        table.setTableHeader(null);
+
+        DefaultCellEditor editor = new DefaultCellEditor(new JTextField());
+        editor.setClickCountToStart(1);
+        table.setDefaultEditor(table.getColumnClass(0), editor);
+
+        JScrollPane pane = new JScrollPane(table);
+        p.add(pane, GBC.eol().insets(5,10,0,0).fill());
+        return p;
+    }
+
+    class ListSettingTableModel extends AbstractTableModel {
+
+        public List<String> getData() {
+            return data;
+        }
+
+        @Override
+        public int getRowCount() {
+            return data.size() + 1;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return 1;
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return data.size() == row ? "" : data.get(row);
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            String s = (String)o;
+            if(row == data.size()) {
+                data.add(s);
+                fireTableRowsInserted(row+1, row+1);
+            } else {
+                data.set(row, s);
+            }
+            fireTableCellUpdated(row, column);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return true;
+        }
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java	(revision 4634)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java	(revision 4634)
@@ -0,0 +1,218 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractListModel;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.Preferences.ListListSetting;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference.PrefEntry;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class ListListEditor extends ExtendedDialog {
+
+    EntryListModel entryModel;
+    List<List<String>> data;
+    PrefEntry entry;
+
+    JList entryList;
+    Integer entryIdx;
+    JTable table;
+
+    ListTableModel tableModel;
+
+    public ListListEditor(final PreferenceTabbedPane gui, PrefEntry entry, ListListSetting setting) {
+        super(gui, tr("Change list of lists setting"), new String[] {tr("OK"), tr("Cancel")});
+        this.entry = entry;
+        List<List<String>> orig = setting.getValue();
+        data = new ArrayList<List<String>>();
+        if (orig != null) {
+            for (List<String> l : orig) {
+                data.add(new ArrayList<String>(l));
+            }
+        }
+        setButtonIcons(new String[] {"ok.png", "cancel.png"});
+        setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(gui, new Dimension(500, 350)));
+        setContent(build(), false);
+    }
+
+    public List<List<String>> getData() {
+        return data;
+    }
+
+    protected JPanel build() {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel(tr("Key: {0}", entry.getKey())), GBC.std(0,0).span(2).weight(1, 0).insets(0,0,5,10));
+
+        JPanel left = new JPanel(new GridBagLayout());
+
+        entryModel = new EntryListModel();
+        entryList = new JList(entryModel);
+        entryList.getSelectionModel().addListSelectionListener(new EntryListener());
+        JScrollPane scroll = new JScrollPane(entryList);
+        left.add(scroll, GBC.eol().fill());
+
+        JToolBar sideButtonTB = new JToolBar(JToolBar.HORIZONTAL);
+        sideButtonTB.setBorderPainted(false);
+        sideButtonTB.setOpaque(false);
+        sideButtonTB.add(new NewEntryAction());
+        RemoveEntryAction removeEntryAction = new RemoveEntryAction();
+        entryList.getSelectionModel().addListSelectionListener(removeEntryAction);
+        sideButtonTB.add(removeEntryAction);
+        left.add(sideButtonTB, GBC.eol());
+
+        left.setPreferredSize(new Dimension(80, 0));
+
+        p.add(left, GBC.std(0,1).fill().weight(0.3, 1.0));
+
+        tableModel = new ListTableModel();
+        table = new JTable(tableModel);
+        table.putClientProperty("terminateEditOnFocusLost", true);
+        table.setTableHeader(null);
+
+        DefaultCellEditor editor = new DefaultCellEditor(new JTextField());
+        editor.setClickCountToStart(1);
+        table.setDefaultEditor(table.getColumnClass(0), editor);
+
+        JScrollPane pane = new JScrollPane(table);
+        pane.setPreferredSize(new Dimension(140, 0));
+        p.add(pane, GBC.std(1,1).insets(5,0,0,0).fill().weight(0.7, 1.0));
+        return p;
+    }
+
+    class EntryListModel extends AbstractListModel {
+        public Object getElementAt(int index) {
+            return (index+1) + ": " + data.get(index).toString();
+        }
+
+        public int getSize() {
+            return data.size();
+        }
+
+        public void add(List<String> l) {
+            data.add(l);
+            fireIntervalAdded(this, data.size() - 1, data.size() - 1);
+        }
+
+        public void remove(int idx) {
+            data.remove(idx);
+            fireIntervalRemoved(this, idx, idx);
+        }
+    }
+
+    class NewEntryAction extends AbstractAction {
+        public NewEntryAction() {
+            putValue(NAME, tr("New"));
+            putValue(SHORT_DESCRIPTION, tr("add entry"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            entryModel.add(new ArrayList<String>());
+        }
+    }
+
+    class RemoveEntryAction extends AbstractAction implements ListSelectionListener {
+        public RemoveEntryAction() {
+            putValue(NAME, tr("Remove"));
+            putValue(SHORT_DESCRIPTION, tr("Remove the selected entry"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+            updateEnabledState();
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(entryList.getSelectedIndices().length == 1);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            int idx = entryList.getSelectedIndices()[0];
+            entryModel.remove(idx);
+        }
+    }
+
+    class EntryListener implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent e) {
+            TableCellEditor editor = table.getCellEditor();
+            if (editor != null) {
+                ((DefaultCellEditor) editor).stopCellEditing();
+            }
+            if (entryList.getSelectedIndices().length != 1) {
+                entryIdx = null;
+                table.setEnabled(false);
+            } else {
+                entryIdx = entryList.getSelectedIndices()[0];
+                table.setEnabled(true);
+            }
+            tableModel.fireTableStructureChanged();
+        }
+    }
+
+    class ListTableModel extends AbstractTableModel {
+
+        private List<String> data() {
+            if (entryIdx == null) return Collections.emptyList();
+            return data.get(entryIdx);
+        }
+
+        @Override
+        public int getRowCount() {
+            return entryIdx == null ? 0 : data().size() + 1;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return 1;
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return data().size() == row ? "" : data().get(row);
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            String s = (String)o;
+            if (row == data().size()) {
+                data().add(s);
+                fireTableRowsInserted(row+1, row+1);
+            } else {
+                data().set(row, s);
+            }
+            fireTableCellUpdated(row, column);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return true;
+        }
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java	(revision 4634)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java	(revision 4634)
@@ -0,0 +1,254 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractListModel;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.Preferences.MapListSetting;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference.PrefEntry;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class MapListEditor extends ExtendedDialog {
+
+    EntryListModel entryModel;
+    PrefEntry entry;
+
+    JList entryList;
+    JTable table;
+    MapTableModel tableModel;
+
+    List<List<String>> dataKeys;
+    List<List<String>> dataValues;
+    Integer entryIdx;
+
+    public MapListEditor(PreferenceTabbedPane gui, PrefEntry entry, MapListSetting setting) {
+        super(gui, tr("Change list of maps setting"), new String[] {tr("OK"), tr("Cancel")});
+        this.entry = entry;
+        List<Map<String, String>> orig = setting.getValue();
+
+        dataKeys = new ArrayList<List<String>>();
+        dataValues = new ArrayList<List<String>>();
+        if (orig != null) {
+            for (Map<String, String> m : orig) {
+                List<String> keys = new ArrayList<String>();
+                List<String> values = new ArrayList<String>();
+                for (Entry<String, String> e : m.entrySet()) {
+                    keys.add(e.getKey());
+                    values.add(e.getValue());
+                }
+                dataKeys.add(keys);
+                dataValues.add(values);
+            }
+        }
+        setButtonIcons(new String[] {"ok.png", "cancel.png"});
+        setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(gui, new Dimension(500, 350)));
+        setContent(build(), false);
+    }
+
+    public List<Map<String,String>> getData() {
+        List<Map<String,String>> data = new ArrayList<Map<String,String>>();
+        for (int i=0; i < dataKeys.size(); ++i) {
+            Map<String,String> m = new LinkedHashMap<String, String>();
+            for (int j=0; j < dataKeys.get(i).size(); ++j) {
+                m.put(dataKeys.get(i).get(j), dataValues.get(i).get(j));
+            }
+            data.add(m);
+        }
+        return data;
+    }
+
+    protected JPanel build() {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel(tr("Key: {0}", entry.getKey())), GBC.std(0,0).span(2).weight(1, 0).insets(0,0,5,10));
+
+        JPanel left = new JPanel(new GridBagLayout());
+
+        entryModel = new EntryListModel();
+        entryList = new JList(entryModel);
+        entryList.getSelectionModel().addListSelectionListener(new EntryListener());
+        JScrollPane scroll = new JScrollPane(entryList);
+        left.add(scroll, GBC.eol().fill());
+
+        JToolBar sideButtonTB = new JToolBar(JToolBar.HORIZONTAL);
+        sideButtonTB.setBorderPainted(false);
+        sideButtonTB.setOpaque(false);
+        sideButtonTB.add(new NewEntryAction());
+        RemoveEntryAction removeEntryAction = new RemoveEntryAction();
+        entryList.getSelectionModel().addListSelectionListener(removeEntryAction);
+        sideButtonTB.add(removeEntryAction);
+        left.add(sideButtonTB, GBC.eol());
+
+        left.setPreferredSize(new Dimension(80, 0));
+
+        p.add(left, GBC.std(0,1).fill().weight(0.3, 1.0));
+
+        tableModel = new MapTableModel();
+        table = new JTable(tableModel);
+        table.putClientProperty("terminateEditOnFocusLost", true);
+        table.getTableHeader().getColumnModel().getColumn(0).setHeaderValue(tr("Key"));
+        table.getTableHeader().getColumnModel().getColumn(1).setHeaderValue(tr("Value"));
+        DefaultCellEditor editor = new DefaultCellEditor(new JTextField());
+        editor.setClickCountToStart(1);
+        table.setDefaultEditor(table.getColumnClass(0), editor);
+
+        JScrollPane pane = new JScrollPane(table);
+        pane.setPreferredSize(new Dimension(140, 0));
+        p.add(pane, GBC.std(1,1).insets(5,0,0,0).fill().weight(0.7, 1.0));
+        return p;
+    }
+
+    class EntryListModel extends AbstractListModel {
+        public Object getElementAt(int index) {
+            return trn("Entry {0}", "Entry {0}", index+1, index+1);
+        }
+
+        public int getSize() {
+            return dataKeys.size();
+        }
+
+        public void add() {
+            dataKeys.add(new ArrayList<String>());
+            dataValues.add(new ArrayList<String>());
+            fireIntervalAdded(this, getSize() - 1, getSize() - 1);
+        }
+
+        public void remove(int idx) {
+            dataKeys.remove(idx);
+            dataValues.remove(idx);
+            fireIntervalRemoved(this, idx, idx);
+        }
+    }
+
+    class NewEntryAction extends AbstractAction {
+        public NewEntryAction() {
+            putValue(NAME, tr("New"));
+            putValue(SHORT_DESCRIPTION, tr("add entry"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            entryModel.add();
+        }
+    }
+
+    class RemoveEntryAction extends AbstractAction implements ListSelectionListener {
+        public RemoveEntryAction() {
+            putValue(NAME, tr("Remove"));
+            putValue(SHORT_DESCRIPTION, tr("Remove the selected entry"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+            updateEnabledState();
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(entryList.getSelectedIndices().length == 1);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            int idx = entryList.getSelectedIndices()[0];
+            entryModel.remove(idx);
+        }
+    }
+
+    class EntryListener implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent e) {
+            TableCellEditor editor = table.getCellEditor();
+            if (editor != null) {
+                ((DefaultCellEditor) editor).stopCellEditing();
+            }
+            if (entryList.getSelectedIndices().length != 1) {
+                entryIdx = null;
+                table.setEnabled(false);
+            } else {
+                entryIdx = entryList.getSelectedIndices()[0];
+                table.setEnabled(true);
+            }
+            tableModel.fireTableDataChanged();
+        }
+    }
+
+    class MapTableModel extends AbstractTableModel {
+        private List<List<String>> data() {
+            if (entryIdx == null) return Collections.emptyList();
+            return Arrays.asList(dataKeys.get(entryIdx), dataValues.get(entryIdx));
+        }
+
+        private int size() {
+            if (entryIdx == null) return 0;
+            return dataKeys.get(entryIdx).size();
+        }
+
+        @Override
+        public int getRowCount() {
+            return entryIdx == null ? 0 : size() + 1;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return 2;
+        }
+
+        @Override
+        public String getColumnName(int column) {
+            return column == 0 ? tr("Key") : tr("Value");
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return size() == row ? "" : data().get(column).get(row);
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            String s = (String) o;
+            if (row == size()) {
+                data().get(0).add("");
+                data().get(1).add("");
+                data().get(column).set(row, s);
+                fireTableRowsInserted(row+1, row+1);
+            } else {
+                data().get(column).set(row, s);
+                fireTableCellUpdated(row, column);
+            }
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return true;
+        }
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/StringEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/StringEditor.java	(revision 4634)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/StringEditor.java	(revision 4634)
@@ -0,0 +1,44 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.data.Preferences.StringSetting;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference.PrefEntry;
+import org.openstreetmap.josm.tools.GBC;
+
+public class StringEditor extends ExtendedDialog {
+
+    PrefEntry entry;
+    JTextField tvalue;
+
+    public StringEditor(final PreferenceTabbedPane gui, PrefEntry entry, StringSetting setting) {
+        super(gui, tr("Change string setting"), new String[] {tr("OK"), tr("Cancel")});
+        this.entry = entry;
+        setButtonIcons(new String[] {"ok.png", "cancel.png"});
+        setContent(build(setting.getValue() == null ? "" : setting.getValue()));
+    }
+
+    public String getData() {
+        return tvalue.getText();
+    }
+
+    protected JPanel build(String orig) {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel(tr("Key: {0}", entry.getKey())), GBC.eol().insets(0,0,5,0));
+
+        p.add(new JLabel(tr("Value: ")), GBC.std());
+        tvalue = new JTextField(orig, 50);
+        p.add(tvalue, GBC.eop().insets(5,0,0,0).fill(GBC.HORIZONTAL));
+
+        return p;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java	(revision 4633)
+++ trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java	(revision 4634)
@@ -264,3 +264,7 @@
         window.setSize(extent);
     }
+
+    public String toString() {
+        return "WindowGeometry{topLeft="+topLeft+",extent="+extent+"}";
+    }
 }
