Index: trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 12987)
@@ -48,9 +48,9 @@
 import org.openstreetmap.josm.data.osm.visitor.paint.ArrowPaintHelper;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
-import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.preferences.StrokeProperty;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -110,10 +110,10 @@
 
     static final CachingProperty<Color> SNAP_HELPER_COLOR
-            = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).cached();
+            = new NamedColorProperty(marktr("draw angle snap"), Color.ORANGE).cached();
 
     static final CachingProperty<Color> HIGHLIGHT_COLOR
-            = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached();
-
-    static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR
+            = new NamedColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached();
+
+    static final AbstractProperty<Color> RUBBER_LINE_COLOR
             = PaintColors.SELECTED.getProperty().getChildColor(marktr("helper line"));
 
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 12987)
@@ -45,5 +45,5 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MainMenu;
@@ -310,6 +310,6 @@
         initialMoveDelay = Config.getPref().getInt("edit.initial-move-delay", 200);
         initialMoveThreshold = Config.getPref().getInt("extrude.initial-move-threshold", 1);
-        mainColor = new ColorProperty(marktr("Extrude: main line"), Color.RED).get();
-        helperColor = new ColorProperty(marktr("Extrude: helper line"), Color.ORANGE).get();
+        mainColor = new NamedColorProperty(marktr("Extrude: main line"), Color.RED).get();
+        helperColor = new NamedColorProperty(marktr("Extrude: helper line"), Color.ORANGE).get();
         helperStrokeDash = GuiHelper.getCustomizedStroke(Config.getPref().get("extrude.stroke.helper-line", "1 4"));
         helperStrokeRA = new BasicStroke(1);
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java	(revision 12987)
@@ -37,5 +37,5 @@
 import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.data.preferences.StrokeProperty;
@@ -161,5 +161,5 @@
     @Override
     protected void readPreferences() {
-        guideColor = new ColorProperty(marktr("improve way accuracy helper line"), Color.RED).get();
+        guideColor = new NamedColorProperty(marktr("improve way accuracy helper line"), Color.RED).get();
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java	(revision 12987)
@@ -35,5 +35,5 @@
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
@@ -105,5 +105,5 @@
     private static final CachingProperty<Double> SNAP_DISTANCE_CHINESE  = new DoubleProperty(prefKey("snap-distance-chinese"), 1).cached();
     private static final CachingProperty<Double> SNAP_DISTANCE_NAUTICAL = new DoubleProperty(prefKey("snap-distance-nautical"), 0.1).cached();
-    private static final CachingProperty<Color> MAIN_COLOR = new ColorProperty(marktr("make parallel helper line"), Color.RED).cached();
+    private static final CachingProperty<Color> MAIN_COLOR = new NamedColorProperty(marktr("make parallel helper line"), Color.RED).cached();
 
     private static final CachingProperty<Map<Modifier, Boolean>> SNAP_MODIFIER_COMBO
Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 12987)
@@ -43,20 +43,21 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.data.preferences.ColorInfo;
+import org.openstreetmap.josm.data.preferences.LongProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
+import org.openstreetmap.josm.data.preferences.PreferencesReader;
+import org.openstreetmap.josm.data.preferences.PreferencesWriter;
+import org.openstreetmap.josm.spi.preferences.AbstractPreferences;
+import org.openstreetmap.josm.spi.preferences.IBaseDirectories;
+import org.openstreetmap.josm.spi.preferences.IPreferences;
 import org.openstreetmap.josm.spi.preferences.ListListSetting;
 import org.openstreetmap.josm.spi.preferences.ListSetting;
-import org.openstreetmap.josm.data.preferences.LongProperty;
 import org.openstreetmap.josm.spi.preferences.MapListSetting;
-import org.openstreetmap.josm.data.preferences.PreferencesReader;
-import org.openstreetmap.josm.data.preferences.PreferencesWriter;
 import org.openstreetmap.josm.spi.preferences.Setting;
 import org.openstreetmap.josm.spi.preferences.StringSetting;
 import org.openstreetmap.josm.io.OfflineAccessException;
 import org.openstreetmap.josm.io.OnlineResource;
-import org.openstreetmap.josm.spi.preferences.AbstractPreferences;
-import org.openstreetmap.josm.spi.preferences.IBaseDirectories;
-import org.openstreetmap.josm.spi.preferences.IPreferences;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.ColorHelper;
@@ -143,5 +144,7 @@
     /**
      * Maps color keys to human readable color name
-     */
+     * @deprecated (since 12987) no longer supported
+     */
+    @Deprecated
     protected final SortedMap<String, String> colornames = new TreeMap<>();
 
@@ -251,4 +254,5 @@
      * @since 12634
      */
+    @SuppressWarnings("deprecation")
     public Preferences(Preferences pref) {
         settingsMap.putAll(pref.settingsMap);
@@ -630,7 +634,41 @@
 
     /**
+     * Get all named colors, including customized and the default ones.
+     * @return a map of all named colors (maps preference key to {@link ColorInfo})
+     */
+    public synchronized Map<String, ColorInfo> getAllNamedColors() {
+        final Map<String, ColorInfo> all = new TreeMap<>();
+        for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
+            if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
+                continue;
+            Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
+                    .map(d -> d.getValue())
+                    .map(lst -> ColorInfo.fromPref(lst, false))
+                    .ifPresent(info -> all.put(e.getKey(), info));
+        }
+        for (final Entry<String, Setting<?>> e : defaultsMap.entrySet()) {
+            if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
+                continue;
+            Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
+                    .map(d -> d.getValue())
+                    .map(lst -> ColorInfo.fromPref(lst, true))
+                    .ifPresent(infoDef -> {
+                        ColorInfo info = all.get(e.getKey());
+                        if (info == null) {
+                            all.put(e.getKey(), infoDef);
+                        } else {
+                            info.setDefaultValue(infoDef.getDefaultValue());
+                        }
+                    });
+        }
+        return all;
+    }
+
+    /**
      * Gets all known colors (preferences starting with the color prefix)
      * @return All colors
-     */
+     * @deprecated (since 12987) replaced by {@link #getAllNamedColors()}
+     */
+    @Deprecated
     public synchronized Map<String, String> getAllColors() {
         final Map<String, String> all = new TreeMap<>();
@@ -975,5 +1013,9 @@
     }
 
-    /* only for preferences */
+    /**
+     * only for preferences
+     * @deprecated (since 12987) no longer supported
+     */
+    @Deprecated
     public synchronized String getColorName(String o) {
         Matcher m = COLOR_LAYER_PATTERN.matcher(o);
@@ -1008,5 +1050,5 @@
     @Deprecated
     public synchronized Color getColor(String colName, String specName, Color def) {
-        String colKey = ColorProperty.getColorKey(colName);
+        String colKey = org.openstreetmap.josm.data.preferences.ColorProperty.getColorKey(colName);
         registerColor(colKey, colName);
         String colStr = specName != null ? get(COLOR_PREFIX+specName) : "";
@@ -1026,5 +1068,7 @@
      * @param colName The name of the color.
      * @since 10824
-     */
+     * @deprecated (since 12987) no longer supported
+     */
+    @Deprecated
     public void registerColor(String colKey, String colName) {
         if (!colKey.equals(colName)) {
@@ -1050,5 +1094,7 @@
      * @return true if the setting was modified
      * @see ColorProperty#put(Color)
-     */
+     * @deprecated (since 12987) no longer supported (see {@link NamedColorProperty})
+     */
+    @Deprecated
     public synchronized boolean putColor(String colKey, Color val) {
         return put(COLOR_PREFIX+colKey, val != null ? ColorHelper.color2html(val, true) : null);
@@ -1435,4 +1481,9 @@
     }
 
+    @Override
+    public Set<String> getKeySet() {
+        return Collections.unmodifiableSet(settingsMap.keySet());
+    }
+
     /**
      * Gets a map of all settings that are currently stored
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java	(revision 12987)
@@ -7,5 +7,5 @@
 
 import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
 
@@ -84,9 +84,9 @@
     private final String name;
     private final Color defaultColor;
-    private final ColorProperty baseProperty;
+    private final NamedColorProperty baseProperty;
     private final CachingProperty<Color> property;
 
     PaintColors(String name, Color defaultColor) {
-        baseProperty = new ColorProperty(name, defaultColor);
+        baseProperty = new NamedColorProperty(name, defaultColor);
         property = baseProperty.cached();
         this.name = name;
@@ -123,5 +123,5 @@
      * @since 10874
      */
-    public ColorProperty getProperty() {
+    public NamedColorProperty getProperty() {
         return baseProperty;
     }
Index: trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java	(revision 12987)
@@ -191,5 +191,5 @@
      */
     public boolean isSet() {
-        return !getPreferences().get(key).isEmpty();
+        return getPreferences().getKeySet().contains(key);
     }
 
@@ -206,5 +206,5 @@
      */
     public void remove() {
-        put(getDefaultValue());
+        getPreferences().put(key, null);
     }
 
Index: trunk/src/org/openstreetmap/josm/data/preferences/ColorInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/ColorInfo.java	(revision 12987)
+++ trunk/src/org/openstreetmap/josm/data/preferences/ColorInfo.java	(revision 12987)
@@ -0,0 +1,151 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import java.awt.Color;
+import java.util.List;
+import org.openstreetmap.josm.tools.ColorHelper;
+
+/**
+ * Data class to hold information on a named color setting.
+ */
+public class ColorInfo {
+
+    private String category;
+    private String source;
+    private String name;
+    private Color value;
+    private Color defaultValue;
+
+    /**
+     * Constructs a new {@code ColorInfo}.
+     */
+    public ColorInfo() {
+    }
+
+    /**
+     * Constructs a new {@code ColorInfo}.
+     * @param category the category of the color setting
+     * @param source the source (related file), can be null
+     * @param name the color name
+     * @param value the color value set in the preferences, null if not set
+     * @param defaultValue the default value for this color setting, can be null
+     * @see org.openstreetmap.josm.data.preferences.NamedColorProperty
+     */
+    public ColorInfo(String category, String source, String name, Color value, Color defaultValue) {
+        this.category = category;
+        this.source = source;
+        this.name = name;
+        this.value = value;
+        this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Get the category.
+     * @return the category
+     */
+    public String getCategory() {
+        return category;
+    }
+
+    /**
+     * Get the source.
+     * @return the source, can be null
+     */
+    public String getSource() {
+        return source;
+    }
+
+    /**
+     * Get the name.
+     * @return the name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the color value in the preferences (if set).
+     * @return the color value, can be null
+     */
+    public Color getValue() {
+        return value;
+    }
+
+    /**
+     * Get the default value for this color setting.
+     * @return the default value, can be null
+     */
+    public Color getDefaultValue() {
+        return defaultValue;
+    }
+
+    /**
+     * Set the category.
+     * @param category the category
+     */
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    /**
+     * Set the source.
+     * @param source the source
+     */
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    /**
+     * Set the name.
+     * @param name the name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Set the color value.
+     * @param value the value
+     */
+    public void setValue(Color value) {
+        this.value = value;
+    }
+
+    /**
+     * Set the default value.
+     * @param defaultValue the default value
+     */
+    public void setDefaultValue(Color defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Constructs a new {@code ColorInfo} from raw preference value.
+     * @param lst the list
+     * @param isDefault if the list represents a default value or not
+     * @return corresponding {@code ColorInfo} object or null in case of invalid input
+     */
+    public static ColorInfo fromPref(List<String> lst, boolean isDefault) {
+        if (lst == null || lst.size() < 4) {
+            return null;
+        }
+        Color clr = ColorHelper.html2color(lst.get(0));
+        if (clr == null) {
+            return null;
+        }
+        ColorInfo info = new ColorInfo();
+        if (isDefault) {
+            info.defaultValue = clr;
+        } else {
+            info.value = clr;
+        }
+        info.category = lst.get(1);
+        info.source = lst.get(2);
+        if (info.source.isEmpty()) {
+            info.source = null;
+        }
+        info.name = lst.get(3);
+        return info;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/preferences/ColorProperty.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/ColorProperty.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/data/preferences/ColorProperty.java	(revision 12987)
@@ -11,5 +11,7 @@
  * A property containing a {@link Color} value.
  * @since 5464
+ * @deprecated (since 12987) replaced by {@link NamedColorProperty}
  */
+@Deprecated
 public class ColorProperty extends AbstractToStringProperty<Color> {
 
Index: trunk/src/org/openstreetmap/josm/data/preferences/FallbackProperty.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/FallbackProperty.java	(revision 12987)
+++ trunk/src/org/openstreetmap/josm/data/preferences/FallbackProperty.java	(revision 12987)
@@ -0,0 +1,99 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * Property that wraps another property along with a fallback property that is used as default value.
+ *
+ * @param <T> The content type
+ * @since 12987
+ */
+public class FallbackProperty<T> extends AbstractProperty<T> {
+
+    private final AbstractProperty<T> property;
+    private final AbstractProperty<T> fallback;
+
+    /**
+     * Constructs a new {@code FallbackProperty}.
+     * @param property the wrapped property
+     * @param fallback fallback property that takes effect in the {@link #get()} method when
+     * {@code property} is not set
+     */
+    public FallbackProperty(AbstractProperty<T> property, AbstractProperty<T> fallback) {
+        super(property.getKey(), null);
+        CheckParameterUtil.ensureParameterNotNull(property, "property");
+        CheckParameterUtil.ensureParameterNotNull(fallback, "fallback");
+        this.property = property;
+        this.fallback = fallback;
+    }
+
+    /**
+     * Get the wrapped property.
+     * @return the wrapped property
+     */
+    public AbstractProperty<T> getDelegateProperty() {
+        return property;
+    }
+
+    /**
+     * Get the fallback property.
+     * @return the fallback property
+     */
+    public AbstractProperty<T> getFallbackProperty() {
+        return fallback;
+    }
+
+    @Override
+    protected void storeDefaultValue() {
+        // Default value hidden in preferences.
+    }
+
+    @Override
+    public boolean isSet() {
+        return property.isSet();
+    }
+
+    @Override
+    public T getDefaultValue() {
+        return fallback.getDefaultValue();
+    }
+
+    @Override
+    public void remove() {
+        property.remove();
+    }
+
+    @Override
+    public T get() {
+        if (property.isSet()) {
+            return property.get();
+        }
+        return fallback.get();
+    }
+
+    @Override
+    public boolean put(T value) {
+        return property.put(value);
+    }
+
+    @Override
+    protected void addListenerImpl(PreferenceChangedListener adapter) {
+        property.addListenerImpl(adapter);
+        fallback.addListenerImpl(adapter);
+    }
+
+    @Override
+    public void addWeakListener(ValueChangeListener<? super T> listener) {
+        property.addWeakListener(listener);
+        fallback.addWeakListener(listener);
+    }
+
+    @Override
+    protected void removeListenerImpl(PreferenceChangedListener adapter) {
+        property.removeListenerImpl(adapter);
+        fallback.removeListenerImpl(adapter);
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/preferences/NamedColorProperty.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/NamedColorProperty.java	(revision 12987)
+++ trunk/src/org/openstreetmap/josm/data/preferences/NamedColorProperty.java	(revision 12987)
@@ -0,0 +1,178 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import java.awt.Color;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ColorHelper;
+
+/**
+ * A property containing a {@link Color} value with additional information associated to it.
+ *
+ * The additional information is used to describe the color in the
+ * {@link org.openstreetmap.josm.gui.preferences.display.ColorPreference}, so it can be recognized
+ * and customized by the user.
+ * @since 12987
+ */
+public class NamedColorProperty extends AbstractProperty<Color> {
+
+    public static final String NAMED_COLOR_PREFIX = "clr.";
+
+    public static final String COLOR_CATEGORY_GENERAL = "general";
+    public static final String COLOR_CATEGORY_MAPPAINT = "mappaint";
+    public static final String COLOR_CATEGORY_LAYER = "layer";
+
+    private final String category;
+    private final String source;
+    private final String name;
+
+    /**
+     * Construct a new {@code NamedColorProperty}.
+     * @param category a category, can be any identifier, but the following values are recognized by
+     * the GUI preferences: {@link #COLOR_CATEGORY_GENERAL}, {@link #COLOR_CATEGORY_MAPPAINT} and
+     * {@link #COLOR_CATEGORY_LAYER}
+     * @param source a filename or similar associated with the color, can be null if not applicable
+     * @param name a short description of the color
+     * @param defaultValue the default value, can be null
+     */
+    public NamedColorProperty(String category, String source, String name, Color defaultValue) {
+        super(getKey(category, source, name), defaultValue);
+        CheckParameterUtil.ensureParameterNotNull(category, "category");
+        CheckParameterUtil.ensureParameterNotNull(name, "name");
+        this.category = category;
+        this.source = source;
+        this.name = name;
+    }
+
+    /**
+     * Construct a new {@code NamedColorProperty}.
+     * @param name a short description of the color
+     * @param defaultValue the default value, can be null
+     */
+    public NamedColorProperty(String name, Color defaultValue) {
+        this(COLOR_CATEGORY_GENERAL, null, name, defaultValue);
+    }
+
+    private static String getKey(String category, String source, String name) {
+        CheckParameterUtil.ensureParameterNotNull(category, "category");
+        CheckParameterUtil.ensureParameterNotNull(name, "name");
+        return NAMED_COLOR_PREFIX + category + "." + (source == null ? "" : source + ".") + name;
+    }
+
+    private List<String> getDefaultValuePref() {
+        return defaultValue == null ? null : getValuePref(defaultValue, category, source, name);
+    }
+
+    @Override
+    public Color get() {
+        List<String> data = getPreferences().getList(getKey(), getDefaultValuePref()); // store default value
+        if (super.isSet() && data != null && !data.isEmpty()) {
+            return ColorHelper.html2color(data.get(0));
+        }
+        return Optional.ofNullable(migrate()).orElse(defaultValue);
+    }
+
+    /**
+     * migrate to new color preferences scheme - remove 4 months after {@link ColorProperty} is removed.
+     * @return the old preferences value
+     */
+    private Color migrate() {
+        String s = getPreferences().get(getOldColorKey(), null);
+        if (s != null) {
+            Color c = ColorHelper.html2color(s);
+            if (c != null) {
+                put(c);
+                return c;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isSet() {
+        get(); // trigger migration
+        return super.isSet();
+    }
+
+    @SuppressWarnings("deprecation")
+    private String getOldColorKey() {
+        switch (category) {
+            case COLOR_CATEGORY_MAPPAINT:
+                return ColorProperty.getColorKey("mappaint." + (source == null ? "MapCSS" : source) + "." + name);
+            case COLOR_CATEGORY_LAYER:
+            {
+                String k = "layer " + (source == null ? "" : source);
+                return ColorProperty.getColorKey(k);
+            }
+            default:
+            {
+                String k = name;
+                if (source != null) {
+                    k = source + "." + k;
+                }
+                return ColorProperty.getColorKey(k);
+            }
+        }
+    }
+
+    /**
+     * Get the category for this color.
+     * @return the category
+     */
+    public String getCategory() {
+        return category;
+    }
+
+    /**
+     * Get the source, i.e.&nbsp;a filename or layer name associated with the color.
+     * May return null if not applicable.
+     * @return the source
+     */
+    public String getSource() {
+        return source;
+    }
+
+    /**
+     * Get the color name (a short description of the color).
+     * @return the color name
+     */
+    public String getName() {
+        return name;
+    }
+
+    private static List<String> getValuePref(Color color, String category, String source, String name) {
+        CheckParameterUtil.ensureParameterNotNull(color, "color");
+        CheckParameterUtil.ensureParameterNotNull(category, "category");
+        CheckParameterUtil.ensureParameterNotNull(name, "name");
+        return Arrays.asList(ColorHelper.color2html(color, true), category, source == null ? "" : source, name);
+    }
+
+    @Override
+    public boolean put(Color value) {
+        return getPreferences().putList(getKey(), value == null ? null : getValuePref(value, category, source, name));
+    }
+
+    /**
+     * Return a more specialized color, that will fall back to this color, if not set explicitly.
+     * @param category the category of the specialized color
+     * @param source the source of the specialized color
+     * @param name the name of the specialized color
+     * @return a {@link FallbackProperty} that will the return the specialized color, if set, but
+     * fall back to this property as default value
+     */
+    public FallbackProperty<Color> getChildColor(String category, String source, String name) {
+        return new FallbackProperty<>(new NamedColorProperty(category, source, name, defaultValue), this);
+    }
+
+    /**
+     * Return a more specialized color, that will fall back to this color, if not set explicitly.
+     * @param name the name of the specialized color
+     * @return a {@link FallbackProperty} that will the return the specialized color, if set, but
+     * fall back to this property as default value
+     */
+    public FallbackProperty<Color> getChildColor(String name) {
+        return getChildColor(category, source, name);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/Severity.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/Severity.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/data/validation/Severity.java	(revision 12987)
@@ -7,5 +7,5 @@
 import java.awt.Color;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -14,9 +14,9 @@
     // CHECKSTYLE.OFF: SingleSpaceSeparator
     /** Error messages */
-    ERROR(1, tr("Errors"), /* ICON(data/) */"error",       new ColorProperty(marktr("validation error"), Color.RED).get()),
+    ERROR(1, tr("Errors"), /* ICON(data/) */"error",       new NamedColorProperty(marktr("validation error"), Color.RED).get()),
     /** Warning messages */
-    WARNING(2, tr("Warnings"), /* ICON(data/) */"warning", new ColorProperty(marktr("validation warning"), Color.YELLOW).get()),
+    WARNING(2, tr("Warnings"), /* ICON(data/) */"warning", new NamedColorProperty(marktr("validation warning"), Color.YELLOW).get()),
     /** Other messages */
-    OTHER(3, tr("Other"), /* ICON(data/) */"other",        new ColorProperty(marktr("validation other"), Color.CYAN).get());
+    OTHER(3, tr("Other"), /* ICON(data/) */"other",        new NamedColorProperty(marktr("validation other"), Color.CYAN).get());
     // CHECKSTYLE.ON: SingleSpaceSeparator
 
Index: trunk/src/org/openstreetmap/josm/gui/MapScaler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapScaler.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/MapScaler.java	(revision 12987)
@@ -15,5 +15,5 @@
 import javax.swing.JComponent;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.help.Helpful;
 
@@ -29,5 +29,5 @@
     private static final int PADDING_RIGHT = 50;
 
-    private static final ColorProperty SCALER_COLOR = new ColorProperty(marktr("scale"), Color.WHITE);
+    private static final NamedColorProperty SCALER_COLOR = new NamedColorProperty(marktr("scale"), Color.WHITE);
 
     /**
Index: trunk/src/org/openstreetmap/josm/gui/MapStatus.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapStatus.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/MapStatus.java	(revision 12987)
@@ -71,6 +71,6 @@
 import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.help.Helpful;
 import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
@@ -80,4 +80,5 @@
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.GBC;
@@ -110,6 +111,6 @@
      * @since 6789
      */
-    public static final ColorProperty PROP_BACKGROUND_COLOR = new ColorProperty(
-            marktr("Status bar background"), "#b8cfe5");
+    public static final NamedColorProperty PROP_BACKGROUND_COLOR = new NamedColorProperty(
+            marktr("Status bar background"), ColorHelper.html2color("#b8cfe5"));
 
     /**
@@ -117,6 +118,6 @@
      * @since 6789
      */
-    public static final ColorProperty PROP_ACTIVE_BACKGROUND_COLOR = new ColorProperty(
-            marktr("Status bar background: active"), "#aaff5e");
+    public static final NamedColorProperty PROP_ACTIVE_BACKGROUND_COLOR = new NamedColorProperty(
+            marktr("Status bar background: active"), ColorHelper.html2color("#aaff5e"));
 
     /**
@@ -124,5 +125,5 @@
      * @since 6789
      */
-    public static final ColorProperty PROP_FOREGROUND_COLOR = new ColorProperty(
+    public static final NamedColorProperty PROP_FOREGROUND_COLOR = new NamedColorProperty(
             marktr("Status bar foreground"), Color.black);
 
@@ -131,5 +132,5 @@
      * @since 6789
      */
-    public static final ColorProperty PROP_ACTIVE_FOREGROUND_COLOR = new ColorProperty(
+    public static final NamedColorProperty PROP_ACTIVE_FOREGROUND_COLOR = new NamedColorProperty(
             marktr("Status bar foreground: active"), Color.black);
 
Index: trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java	(revision 12987)
@@ -12,5 +12,5 @@
 
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 
@@ -21,5 +21,5 @@
 public class AutoFilterButton extends JButton {
 
-    private static final ColorProperty PROP_COLOR = new ColorProperty("auto.filter.button.color", new Color(0, 160, 160));
+    private static final NamedColorProperty PROP_COLOR = new NamedColorProperty("auto.filter.button.color", new Color(0, 160, 160));
 
     private final AutoFilter filter;
Index: trunk/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java	(revision 12987)
@@ -6,5 +6,5 @@
 import java.awt.Color;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 
 /**
@@ -82,8 +82,8 @@
     FGCOLOR_MEMBER_REMOVE(marktr("Conflict foreground: remove member"), Color.black);
 
-    private final ColorProperty property;
+    private final NamedColorProperty property;
 
     ConflictColors(String name, Color defaultColor) {
-        property = new ColorProperty(name, defaultColor);
+        property = new NamedColorProperty(name, defaultColor);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(revision 12987)
@@ -52,5 +52,5 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
@@ -78,6 +78,6 @@
 public final class ConflictDialog extends ToggleDialog implements ActiveLayerChangeListener, IConflictListener, DataSelectionListener {
 
-    private static final ColorProperty CONFLICT_COLOR = new ColorProperty(marktr("conflict"), Color.GRAY);
-    private static final ColorProperty BACKGROUND_COLOR = new ColorProperty(marktr("background"), Color.BLACK);
+    private static final NamedColorProperty CONFLICT_COLOR = new NamedColorProperty(marktr("conflict"), Color.GRAY);
+    private static final NamedColorProperty BACKGROUND_COLOR = new NamedColorProperty(marktr("background"), Color.BLACK);
 
     /** the collection of conflicts displayed by this conflict dialog */
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java	(revision 12987)
@@ -24,5 +24,5 @@
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 
 /**
@@ -33,8 +33,8 @@
 
     private static final CachingProperty<Color> SELECTED_FG
-            = new ColorProperty(marktr("Discardable key: selection Foreground"), Color.GRAY).cached();
+            = new NamedColorProperty(marktr("Discardable key: selection Foreground"), Color.GRAY).cached();
     private static final CachingProperty<Color> SELECTED_BG;
     private static final CachingProperty<Color> NORMAL_FG
-            = new ColorProperty(marktr("Discardable key: foreground"), Color.GRAY).cached();
+            = new NamedColorProperty(marktr("Discardable key: foreground"), Color.GRAY).cached();
     private static final CachingProperty<Color> NORMAL_BG;
     private static final CachingProperty<Boolean> DISCARDABLE
@@ -42,7 +42,7 @@
 
     static {
-        SELECTED_BG = new ColorProperty(marktr("Discardable key: selection Background"),
+        SELECTED_BG = new NamedColorProperty(marktr("Discardable key: selection Background"),
                 Optional.ofNullable(UIManager.getColor("Table.selectionBackground")).orElse(Color.BLUE)).cached();
-        NORMAL_BG = new ColorProperty(marktr("Discardable key: background"),
+        NORMAL_BG = new NamedColorProperty(marktr("Discardable key: background"),
                 Optional.ofNullable(UIManager.getColor("Table.background")).orElse(Color.WHITE)).cached();
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 12987)
@@ -26,5 +26,5 @@
 import org.openstreetmap.josm.data.gpx.GpxTrack;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.MapView;
@@ -100,5 +100,5 @@
 
     @Override
-    protected ColorProperty getBaseColorProperty() {
+    protected NamedColorProperty getBaseColorProperty() {
         return GpxDrawHelper.DEFAULT_COLOR;
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/Layer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/Layer.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/layer/Layer.java	(revision 12987)
@@ -29,5 +29,5 @@
 import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
@@ -204,8 +204,7 @@
      */
     public AbstractProperty<Color> getColorProperty() {
-        ColorProperty base = getBaseColorProperty();
+        NamedColorProperty base = getBaseColorProperty();
         if (base != null) {
-            // cannot cache this - name may change.
-            return base.getChildColor("layer " + getName());
+            return base.getChildColor(NamedColorProperty.COLOR_CATEGORY_LAYER, getName(), base.getName());
         } else {
             return null;
@@ -218,5 +217,5 @@
      * @since 10824
      */
-    protected ColorProperty getBaseColorProperty() {
+    protected NamedColorProperty getBaseColorProperty() {
         return null;
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 12987)
@@ -81,6 +81,6 @@
 import org.openstreetmap.josm.data.osm.visitor.paint.Rendering;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.data.projection.Projection;
@@ -153,6 +153,6 @@
     public static final StringProperty PROPERTY_SAVE_EXTENSION = new StringProperty("save.extension.osm", "osm");
 
-    private static final ColorProperty PROPERTY_BACKGROUND_COLOR = new ColorProperty(marktr("background"), Color.BLACK);
-    private static final ColorProperty PROPERTY_OUTSIDE_COLOR = new ColorProperty(marktr("outside downloaded area"), Color.YELLOW);
+    private static final NamedColorProperty PROPERTY_BACKGROUND_COLOR = new NamedColorProperty(marktr("background"), Color.BLACK);
+    private static final NamedColorProperty PROPERTY_OUTSIDE_COLOR = new NamedColorProperty(marktr("outside downloaded area"), Color.YELLOW);
 
     /** List of recent relations */
Index: trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 12987)
@@ -43,5 +43,5 @@
 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
 import org.openstreetmap.josm.data.gpx.WayPoint;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.MapViewState;
@@ -69,5 +69,5 @@
      * @since 10824
      */
-    public static final ColorProperty DEFAULT_COLOR = new ColorProperty(marktr("gps point"), Color.magenta);
+    public static final NamedColorProperty DEFAULT_COLOR = new NamedColorProperty(marktr("gps point"), Color.magenta);
 
     private final GpxData data;
@@ -266,5 +266,8 @@
     public Color getColor(String layerName, boolean ignoreCustom) {
         if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
-            return DEFAULT_COLOR.getChildColor(specName(layerName)).get();
+            return DEFAULT_COLOR.getChildColor(
+                    NamedColorProperty.COLOR_CATEGORY_LAYER,
+                    layerName,
+                    DEFAULT_COLOR.getName()).get();
         } else {
             return null;
Index: trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 12987)
@@ -38,5 +38,5 @@
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapView;
@@ -79,5 +79,5 @@
 
     private static final Color DEFAULT_COLOR = Color.magenta;
-    private static final ColorProperty COLOR_PROPERTY = new ColorProperty(marktr("gps marker"), DEFAULT_COLOR);
+    private static final NamedColorProperty COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
 
     /**
@@ -163,5 +163,5 @@
 
     @Override
-    protected ColorProperty getBaseColorProperty() {
+    protected NamedColorProperty getBaseColorProperty() {
         return COLOR_PROPERTY;
     }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 12987)
@@ -18,5 +18,5 @@
 import java.util.Locale;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.mappaint.Keyword;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
@@ -1134,5 +1134,10 @@
         LOOKAHEAD(2)
         pref=ident() t=<HEXCOLOR>
-        { return new ColorProperty("mappaint." + (sheet == null ? "MapCSS" : sheet.title) + "." + pref, t.image).get(); }
+        {
+            return new NamedColorProperty(
+                    NamedColorProperty.COLOR_CATEGORY_MAPPAINT,
+                    sheet == null ? "MapCSS" : sheet.title, pref,
+                    ColorHelper.html2color(t.image)).get();
+        }
     |
         t=<IDENT> { return new Keyword(t.image); }
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java	(revision 12987)
@@ -29,5 +29,5 @@
 import javax.swing.table.DefaultTableModel;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.spi.preferences.ListListSetting;
 import org.openstreetmap.josm.spi.preferences.ListSetting;
@@ -336,8 +336,8 @@
     static final class SettingCellRenderer extends DefaultTableCellRenderer {
         private final Color backgroundColor = UIManager.getColor("Table.background");
-        private final Color changedColor = new ColorProperty(
+        private final Color changedColor = new NamedColorProperty(
                          marktr("Advanced Background: Changed"),
                          new Color(200, 255, 200)).get();
-        private final Color nonDefaultColor = new ColorProperty(
+        private final Color nonDefaultColor = new NamedColorProperty(
                             marktr("Advanced Background: NonDefault"),
                             new Color(255, 255, 200)).get();
Index: trunk/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java	(revision 12987)
@@ -13,9 +13,10 @@
 import java.text.Collator;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
 
 import javax.swing.BorderFactory;
@@ -34,5 +35,7 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.preferences.ColorInfo;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.validation.Severity;
 import org.openstreetmap.josm.gui.MapScaler;
@@ -52,8 +55,11 @@
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.I18n;
 
 /**
  * Color preferences.
+ *
+ * GUI preference to let the user customize named colors.
+ * @see NamedColorProperty
  */
 public class ColorPreference implements SubPreferenceSetting {
@@ -78,17 +84,68 @@
     private static class ColorEntry {
         String key;
-        Color color;
-
-        ColorEntry(String key, String colorHtml) {
+        ColorInfo info;
+
+        ColorEntry(String key, ColorInfo info) {
             CheckParameterUtil.ensureParameterNotNull(key, "key");
+            CheckParameterUtil.ensureParameterNotNull(info, "info");
             this.key = key;
-            this.color = ColorHelper.html2color(colorHtml);
-            if (this.color == null) {
-                Logging.warn("Unable to get color from '"+colorHtml+"' for color preference '"+key+'\'');
-            }
-        }
-
+            this.info = info;
+        }
+
+        /**
+         * Get a description of the color based on the given info.
+         * @return a description of the color
+         */
         public String getDisplay() {
-            return Main.pref.getColorName(key);
+            switch (info.getCategory()) {
+                case NamedColorProperty.COLOR_CATEGORY_LAYER:
+                    String v = null;
+                    if (info.getSource() != null) {
+                        v = info.getSource();
+                    }
+                    if (!info.getName().isEmpty()) {
+                        if (v == null) {
+                            v = tr(I18n.escape(info.getName()));
+                        } else {
+                            v += " - " + tr(I18n.escape(info.getName()));
+                        }
+                    }
+                    return tr("Layer: {0}", v);
+                case NamedColorProperty.COLOR_CATEGORY_MAPPAINT:
+                    if (info.getSource() != null)
+                        return tr("Paint style {0}: {1}", tr(I18n.escape(info.getSource())), tr(info.getName()));
+                    // fall through
+                default:
+                    if (info.getSource() != null)
+                        return tr(I18n.escape(info.getSource())) + " - " + tr(I18n.escape(info.getName()));
+                    else
+                        return tr(I18n.escape(info.getName()));
+            }
+        }
+
+        /**
+         * Get the color value to display.
+         * Either value (if set) or default value.
+         * @return the color value to display
+         */
+        public Color getDisplayColor() {
+            return Optional.ofNullable(info.getValue()).orElse(info.getDefaultValue());
+        }
+
+        /**
+         * Check if color has been customized by the user or not.
+         * @return true if the color is at its default value, false if it is customized by the user.
+         */
+        public boolean isDefault() {
+            return info.getValue() == null || Objects.equals(info.getValue(),  info.getDefaultValue());
+        }
+
+        /**
+         * Convert to a {@link NamedColorProperty}.
+         * @return a {@link NamedColorProperty}
+         */
+        public NamedColorProperty toProperty() {
+            return new NamedColorProperty(info.getCategory(), info.getSource(),
+                    info.getName(), info.getDefaultValue());
         }
     }
@@ -143,5 +200,5 @@
         @Override
         public Object getValueAt(int rowIndex, int columnIndex) {
-            return columnIndex == 0 ? data.get(rowIndex) : data.get(rowIndex).color;
+            return columnIndex == 0 ? data.get(rowIndex) : data.get(rowIndex).getDisplayColor();
         }
 
@@ -159,7 +216,46 @@
         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
             if (columnIndex == 1 && aValue instanceof Color) {
-                data.get(rowIndex).color = (Color) aValue;
+                data.get(rowIndex).info.setValue((Color) aValue);
                 fireTableRowsUpdated(rowIndex, rowIndex);
             }
+        }
+    }
+
+    /**
+     * Set the colors to be shown in the preference table. This method creates a table model if
+     * none exists and overwrites all existing values.
+     * @param colorMap the map holding the colors
+     * (key = preference key, value = {@link ColorInfo} instance)
+     */
+    public void setColors(Map<String, ColorInfo> colorMap) {
+        if (tableModel == null) {
+            tableModel = new ColorTableModel();
+        }
+        tableModel.clear();
+
+        // fill model with colors:
+        colorMap.entrySet().stream()
+                .map(e -> new ColorEntry(e.getKey(), e.getValue()))
+                .sorted((e1, e2) -> {
+                    int cat = Integer.compare(
+                            getCategroyPriority(e1.info.getCategory()),
+                            getCategroyPriority(e2.info.getCategory()));
+                    if (cat != 0) return cat;
+                    return Collator.getInstance().compare(e1.getDisplay(), e2.getDisplay());
+                })
+                .forEach(tableModel::addEntry);
+
+        if (this.colors != null) {
+            this.colors.repaint();
+        }
+
+    }
+
+    private static int getCategroyPriority(String category) {
+        switch (category) {
+            case NamedColorProperty.COLOR_CATEGORY_GENERAL: return 1;
+            case NamedColorProperty.COLOR_CATEGORY_MAPPAINT: return 2;
+            case NamedColorProperty.COLOR_CATEGORY_LAYER: return 3;
+            default: return 4;
         }
     }
@@ -171,37 +267,18 @@
      * (key = color id (without prefixes, so only <code>background</code>; not <code>color.background</code>),
      * value = html representation of the color.
-     */
+     * @deprecated (since 12987) replaced by {@link #setColors(java.util.Map)}
+     */
+    @Deprecated
     public void setColorModel(Map<String, String> colorMap) {
-        if (tableModel == null) {
-            tableModel = new ColorTableModel();
-        }
-
-        tableModel.clear();
-        // fill model with colors:
-        List<ColorEntry> colorKeyList = new ArrayList<>();
-        List<ColorEntry> colorKeyListMappaint = new ArrayList<>();
-        List<ColorEntry> colorKeyListLayer = new ArrayList<>();
-        for (Map.Entry<String, String> e : colorMap.entrySet()) {
-            String key = e.getKey();
-            String html = e.getValue();
-            if (key.startsWith("layer.")) {
-                colorKeyListLayer.add(new ColorEntry(key, html));
-            } else if (key.startsWith("mappaint.")) {
-                colorKeyListMappaint.add(new ColorEntry(key, html));
-            } else {
-                colorKeyList.add(new ColorEntry(key, html));
-            }
-        }
-        addColorRows(colorKeyList);
-        addColorRows(colorKeyListMappaint);
-        addColorRows(colorKeyListLayer);
-        if (this.colors != null) {
-            this.colors.repaint();
-        }
-    }
-
-    private void addColorRows(List<ColorEntry> entries) {
-        Collections.sort(entries, (e1, e2) -> Collator.getInstance().compare(e1.getDisplay(), e2.getDisplay()));
-        entries.forEach(tableModel::addEntry);
+        setColors(colorMap.entrySet().stream().collect(Collectors.toMap(e->e.getKey(), e->
+            new ColorInfo(NamedColorProperty.COLOR_CATEGORY_GENERAL, null, e.getKey(), ColorHelper.html2color(e.getValue()), null))));
+    }
+
+    /**
+     * Returns a map with the colors in the table (key = preference key, value = color info).
+     * @return a map holding the colors.
+     */
+    public Map<String, ColorInfo> getColors() {
+        return tableModel.getData().stream().collect(Collectors.toMap(e -> e.key, e -> e.info));
     }
 
@@ -209,9 +286,11 @@
      * Returns a map with the colors in the table (key = color name without prefix, value = html color code).
      * @return a map holding the colors.
-     */
+     * @deprecated replaced by {@link #getColors()}
+     */
+    @Deprecated
     public Map<String, String> getColorModel() {
         Map<String, String> colorMap = new HashMap<>();
         for (ColorEntry e : tableModel.getData()) {
-            colorMap.put(e.key, ColorHelper.color2html(e.color));
+            colorMap.put(e.key, ColorHelper.color2html(e.getDisplayColor()));
         }
         return colorMap;
@@ -221,5 +300,5 @@
     public void addGui(final PreferenceTabbedPane gui) {
         fixColorPrefixes();
-        setColorModel(Main.pref.getAllColors());
+        setColors(Main.pref.getAllNamedColors());
 
         colorEdit = new JButton(tr("Choose"));
@@ -227,5 +306,5 @@
             int sel = colors.getSelectedRow();
             ColorEntry ce = tableModel.getEntry(sel);
-            JColorChooser chooser = new JColorChooser(ce.color);
+            JColorChooser chooser = new JColorChooser(ce.getDisplayColor());
             int answer = JOptionPane.showConfirmDialog(
                     gui, chooser,
@@ -293,5 +372,5 @@
                     ColorEntry e = (ColorEntry) value;
                     label.setText(e.getDisplay());
-                    if (!Objects.equals(e.color, Main.pref.getDefaultColor(e.key))) {
+                    if (!e.isDefault()) {
                         label.setFont(label.getFont().deriveFont(Font.BOLD));
                     } else {
@@ -339,5 +418,5 @@
 
     Boolean isRemoveColor(int row) {
-        return tableModel.getEntry(row).key.startsWith("layer.");
+        return tableModel.getEntry(row).info.getCategory().equals(NamedColorProperty.COLOR_CATEGORY_LAYER);
     }
 
@@ -361,9 +440,12 @@
         boolean ret = false;
         for (ColorEntry d : tableModel.getDeleted()) {
-            Main.pref.putColor(d.key, null);
+            d.toProperty().remove();
         }
         for (ColorEntry e : tableModel.getData()) {
-            if (Main.pref.putColor(e.key, e.color) && e.key.startsWith("mappaint.")) {
-                ret = true;
+            if (!e.isDefault()) {
+                if (e.toProperty().put(e.info.getValue())
+                        && e.key.startsWith("mappaint.")) {
+                    ret = true;
+                }
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java	(revision 12987)
@@ -26,4 +26,5 @@
 import org.openstreetmap.josm.actions.ExpertToggleAction;
 import org.openstreetmap.josm.data.PreferencesUtils;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
 import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
@@ -329,5 +330,6 @@
                 // ask the GPX draw for the correct color of that layer ( if there is one )
                 if (null != layerName) {
-                    color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(layerName).get();
+                    color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(
+                            NamedColorProperty.COLOR_CATEGORY_LAYER, layerName, GpxDrawHelper.DEFAULT_COLOR.getName()).get();
                 } else {
                     color = GpxDrawHelper.DEFAULT_COLOR.getDefaultValue();
Index: trunk/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreference.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreference.java	(revision 12987)
@@ -60,5 +60,5 @@
 import org.openstreetmap.josm.data.imagery.OffsetBookmark;
 import org.openstreetmap.josm.data.imagery.Shape;
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
@@ -248,5 +248,5 @@
         private static class ImageryURLTableCellRenderer extends DefaultTableCellRenderer {
 
-            private static final ColorProperty IMAGERY_BACKGROUND_COLOR = new ColorProperty(
+            private static final NamedColorProperty IMAGERY_BACKGROUND_COLOR = new NamedColorProperty(
                     marktr("Imagery Background: Default"),
                     new Color(200, 255, 200));
Index: trunk/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java	(revision 12987)
@@ -45,5 +45,5 @@
 import javax.swing.table.TableRowSorter;
 
-import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
@@ -160,8 +160,8 @@
     private class ShortcutTableCellRenderer extends DefaultTableCellRenderer {
 
-        private final transient ColorProperty SHORTCUT_BACKGROUND_USER_COLOR = new ColorProperty(
+        private final transient NamedColorProperty SHORTCUT_BACKGROUND_USER_COLOR = new NamedColorProperty(
                 marktr("Shortcut Background: User"),
                 new Color(200, 255, 200));
-        private final transient ColorProperty SHORTCUT_BACKGROUND_MODIFIED_COLOR = new ColorProperty(
+        private final transient NamedColorProperty SHORTCUT_BACKGROUND_MODIFIED_COLOR = new NamedColorProperty(
                 marktr("Shortcut Background: Modified"),
                 new Color(255, 255, 200));
Index: trunk/src/org/openstreetmap/josm/spi/preferences/IPreferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/spi/preferences/IPreferences.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/spi/preferences/IPreferences.java	(revision 12987)
@@ -5,4 +5,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
@@ -234,3 +235,8 @@
     boolean putListOfMaps(String key, List<Map<String, String>> value);
 
+    /**
+     * Get the set of all keys that are mapped to a value in this preferences.
+     * @return the set of all keys
+     */
+    Set<String> getKeySet();
 }
Index: trunk/src/org/openstreetmap/josm/spi/preferences/ListSetting.java
===================================================================
--- trunk/src/org/openstreetmap/josm/spi/preferences/ListSetting.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/spi/preferences/ListSetting.java	(revision 12987)
@@ -37,5 +37,5 @@
     private void consistencyTest() {
         if (value != null && value.contains(null))
-            throw new IllegalArgumentException("Error: Null as list element in preference setting");
+            throw new IllegalArgumentException("Error: Null as list element in preference setting: " + value);
     }
 
Index: trunk/src/org/openstreetmap/josm/spi/preferences/MemoryPreferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/spi/preferences/MemoryPreferences.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/spi/preferences/MemoryPreferences.java	(revision 12987)
@@ -2,7 +2,9 @@
 package org.openstreetmap.josm.spi.preferences;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
@@ -41,4 +43,9 @@
 
     @Override
+    public Set<String> getKeySet() {
+        return Collections.unmodifiableSet(settings.keySet());
+    }
+
+    @Override
     public void addPreferenceChangeListener(PreferenceChangedListener listener) {
         // do nothing
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 12986)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 12987)
@@ -45,4 +45,5 @@
 import java.util.List;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -1731,3 +1732,20 @@
         }
     }
+
+    /**
+     * Helper method to replace the "<code>instanceof</code>-check and cast" pattern.
+     *
+     * @param <T> the type for the instanceof check and cast
+     * @param o the object to check and cast
+     * @param klass the class T
+     * @return {@link Optional} containing the result of the cast, if it is possible, an empty
+     * Optional otherwise
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Optional<T> instanceOfAndCast(Object o, Class<T> klass) {
+        if (klass.isInstance(o))
+            return Optional.of((T) o);
+        return Optional.empty();
+    }
+
 }
