Index: trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java	(revision 12983)
+++ 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 12983)
+++ 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);
+    }
+}
