commit 31b90bef7c1f7cb6c98c6249a56de7a5f6606657
Author: Simon Legner <Simon.Legner@gmail.com>
Date:   Sun Oct 21 21:36:09 2018 +0200

    see #16869 - Migrate some PreferenceChangedListener to ValueChangeListener

diff --git a/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java b/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java
index 14243d7b1..1a480a994 100644
--- a/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java
+++ b/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java
@@ -5,9 +5,7 @@
 
 import javax.swing.JCheckBoxMenuItem;
 
-import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
 import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 
 /**
@@ -16,7 +14,7 @@
  * A user action will just change a preference value. To take any real action,
  * register another {@link PreferenceChangedListener} for the given preference key.
  */
-public class PreferenceToggleAction extends JosmAction implements PreferenceChangedListener {
+public class PreferenceToggleAction extends JosmAction {
 
     private final JCheckBoxMenuItem checkbox;
     private final BooleanProperty pref;
@@ -34,7 +32,7 @@ public PreferenceToggleAction(String name, String tooltip, String prefKey, boole
         this.pref = new BooleanProperty(prefKey, prefDefault);
         checkbox = new JCheckBoxMenuItem(this);
         checkbox.setSelected(pref.get());
-        Preferences.main().addWeakKeyPreferenceChangeListener(prefKey, this);
+        this.pref.addWeakListener(event -> checkbox.setSelected(event.getProperty().get()));
     }
 
     @Override
@@ -49,9 +47,4 @@ public void actionPerformed(ActionEvent e) {
     public JCheckBoxMenuItem getCheckbox() {
         return checkbox;
     }
-
-    @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        checkbox.setSelected(pref.get());
-    }
 }
diff --git a/src/org/openstreetmap/josm/actions/mapmode/DrawSnapHelper.java b/src/org/openstreetmap/josm/actions/mapmode/DrawSnapHelper.java
index 1df0a64c2..1e83eae21 100644
--- a/src/org/openstreetmap/josm/actions/mapmode/DrawSnapHelper.java
+++ b/src/org/openstreetmap/josm/actions/mapmode/DrawSnapHelper.java
@@ -17,13 +17,13 @@
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JPopupMenu;
 
-import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.preferences.ListProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.MapViewState;
@@ -31,7 +31,6 @@
 import org.openstreetmap.josm.gui.draw.MapViewPath;
 import org.openstreetmap.josm.gui.draw.SymbolShape;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
-import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -64,7 +63,8 @@ public void mouseClicked(MouseEvent e) {
         };
     }
 
-    private static final String DRAW_ANGLESNAP_ANGLES = "draw.anglesnap.angles";
+    private static final ListProperty DRAW_ANGLESNAP_ANGLES = new ListProperty("draw.anglesnap.angles",
+            Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"));
 
     private static final class RepeatedAction extends AbstractAction {
         RepeatedAction(DrawSnapHelper snapHelper) {
@@ -233,12 +233,11 @@ public void init() {
         absoluteFix = false;
 
         computeSnapAngles();
-        Preferences.main().addWeakKeyPreferenceChangeListener(DRAW_ANGLESNAP_ANGLES, e -> this.computeSnapAngles());
+        DRAW_ANGLESNAP_ANGLES.addWeakListener(event -> computeSnapAngles());
     }
 
     private void computeSnapAngles() {
-        snapAngles = Config.getPref().getList(DRAW_ANGLESNAP_ANGLES,
-                Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"))
+        snapAngles = DRAW_ANGLESNAP_ANGLES.get()
                 .stream()
                 .mapToDouble(DrawSnapHelper::parseSnapAngle)
                 .flatMap(s -> DoubleStream.of(s, 360-s))
@@ -259,7 +258,7 @@ private static double parseSnapAngle(String string) {
      * @param angles The angles
      */
     public void saveAngles(String... angles) {
-        Config.getPref().putList(DRAW_ANGLESNAP_ANGLES, Arrays.asList(angles));
+        DRAW_ANGLESNAP_ANGLES.put(Arrays.asList(angles));
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/data/osm/ChangesetCache.java b/src/org/openstreetmap/josm/data/osm/ChangesetCache.java
index a86768846..23ed1cf1d 100644
--- a/src/org/openstreetmap/josm/data/osm/ChangesetCache.java
+++ b/src/org/openstreetmap/josm/data/osm/ChangesetCache.java
@@ -12,9 +12,7 @@
 import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.data.UserIdentityManager;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
 
 /**
@@ -31,7 +29,7 @@
  * clears itself if the OSM API URL is changed in the preferences.
  *
  */
-public final class ChangesetCache implements PreferenceChangedListener {
+public final class ChangesetCache {
     /** the unique instance */
     private static final ChangesetCache INSTANCE = new ChangesetCache();
 
@@ -44,7 +42,7 @@
      * Constructs a new {@code ChangesetCache}.
      */
     private ChangesetCache() {
-        Config.getPref().addPreferenceChangeListener(this);
+        OsmApi.SERVER_URL_PROPERTY.addListener(event -> clear());
     }
 
     /**
@@ -252,18 +250,4 @@ public void clear() {
                     object -> UserIdentityManager.getInstance().isCurrentUser(object.getUser())));
         }
     }
-
-    /* ------------------------------------------------------------------------- */
-    /* interface PreferenceChangedListener                                       */
-    /* ------------------------------------------------------------------------- */
-    @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
-            return;
-
-        // clear the cache when the API url changes
-        if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) {
-            clear();
-        }
-    }
 }
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
index cc22c4ec3..aa2057bf3 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
@@ -12,6 +12,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -23,14 +24,14 @@
 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
+import org.openstreetmap.josm.data.preferences.ListProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.tools.Geometry;
 import org.openstreetmap.josm.tools.Geometry.AreaAndPerimeter;
 import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Pair;
 
 /**
  * Multipolygon data used to represent complex areas, see <a href="https://wiki.openstreetmap.org/wiki/Relation:multipolygon">wiki</a>.
@@ -41,22 +42,22 @@
     /** preference key for a collection of roles which indicate that the respective member belongs to an
      * <em>outer</em> polygon. Default is <code>outer</code>.
      */
-    public static final String PREF_KEY_OUTER_ROLES = "mappaint.multipolygon.outer.roles";
+    public static final ListProperty PREF_KEY_OUTER_ROLES = new ListProperty("mappaint.multipolygon.outer.roles", Collections.emptyList());
 
     /** preference key for collection of role prefixes which indicate that the respective
      *  member belongs to an <em>outer</em> polygon. Default is empty.
      */
-    public static final String PREF_KEY_OUTER_ROLE_PREFIXES = "mappaint.multipolygon.outer.role-prefixes";
+    public static final ListProperty PREF_KEY_OUTER_ROLE_PREFIXES = new ListProperty("mappaint.multipolygon.outer.role-prefixes", Collections.emptyList());
 
     /** preference key for a collection of roles which indicate that the respective member belongs to an
      * <em>inner</em> polygon. Default is <code>inner</code>.
      */
-    public static final String PREF_KEY_INNER_ROLES = "mappaint.multipolygon.inner.roles";
+    public static final ListProperty PREF_KEY_INNER_ROLES = new ListProperty("mappaint.multipolygon.inner.roles", Collections.emptyList());
 
     /** preference key for collection of role prefixes which indicate that the respective
      *  member belongs to an <em>inner</em> polygon. Default is empty.
      */
-    public static final String PREF_KEY_INNER_ROLE_PREFIXES = "mappaint.multipolygon.inner.role-prefixes";
+    public static final ListProperty PREF_KEY_INNER_ROLE_PREFIXES = new ListProperty("mappaint.multipolygon.inner.role-prefixes", Collections.emptyList());
 
     /**
      * <p>Kind of strategy object which is responsible for deciding whether a given
@@ -66,7 +67,7 @@
      * <p>The decision is taken based on preference settings, see the four preference keys
      * above.</p>
      */
-    private static class MultipolygonRoleMatcher implements PreferenceChangedListener {
+    private static class MultipolygonRoleMatcher {
         private final List<String> outerExactRoles = new ArrayList<>();
         private final List<String> outerRolePrefixes = new ArrayList<>();
         private final List<String> innerExactRoles = new ArrayList<>();
@@ -97,33 +98,24 @@ private static void setNormalized(Collection<String> literals, List<String> targ
         private void initFromPreferences() {
             initDefaults();
             if (Config.getPref() == null) return;
-            Collection<String> literals;
-            literals = Config.getPref().getList(PREF_KEY_OUTER_ROLES);
-            if (literals != null && !literals.isEmpty()) {
-                setNormalized(literals, outerExactRoles);
-            }
-            literals = Config.getPref().getList(PREF_KEY_OUTER_ROLE_PREFIXES);
-            if (literals != null && !literals.isEmpty()) {
-                setNormalized(literals, outerRolePrefixes);
-            }
-            literals = Config.getPref().getList(PREF_KEY_INNER_ROLES);
-            if (literals != null && !literals.isEmpty()) {
-                setNormalized(literals, innerExactRoles);
-            }
-            literals = Config.getPref().getList(PREF_KEY_INNER_ROLE_PREFIXES);
-            if (literals != null && !literals.isEmpty()) {
-                setNormalized(literals, innerRolePrefixes);
-            }
+            Stream.of(
+                    new Pair<>(PREF_KEY_OUTER_ROLES, outerExactRoles),
+                    new Pair<>(PREF_KEY_OUTER_ROLE_PREFIXES, outerRolePrefixes),
+                    new Pair<>(PREF_KEY_INNER_ROLES, innerExactRoles),
+                    new Pair<>(PREF_KEY_INNER_ROLE_PREFIXES, innerRolePrefixes)
+            ).forEach(pair -> {
+                final Collection<String> literals = pair.a.get();
+                if (literals != null && !literals.isEmpty()) {
+                    setNormalized(literals, pair.b);
+                }
+            });
         }
 
-        @Override
-        public void preferenceChanged(PreferenceChangeEvent evt) {
-            if (PREF_KEY_INNER_ROLE_PREFIXES.equals(evt.getKey()) ||
-                    PREF_KEY_INNER_ROLES.equals(evt.getKey()) ||
-                    PREF_KEY_OUTER_ROLE_PREFIXES.equals(evt.getKey()) ||
-                    PREF_KEY_OUTER_ROLES.equals(evt.getKey())) {
-                initFromPreferences();
-            }
+        private void registerPreferenceListeners() {
+            PREF_KEY_OUTER_ROLES.addListener(event -> initFromPreferences());
+            PREF_KEY_OUTER_ROLE_PREFIXES.addListener(event -> initFromPreferences());
+            PREF_KEY_INNER_ROLES.addListener(event -> initFromPreferences());
+            PREF_KEY_INNER_ROLE_PREFIXES.addListener(event -> initFromPreferences());
         }
 
         boolean isOuterRole(String role) {
@@ -158,8 +150,8 @@ private static synchronized MultipolygonRoleMatcher getMultipolygonRoleMatcher()
         if (roleMatcher == null) {
             roleMatcher = new MultipolygonRoleMatcher();
             if (Config.getPref() != null) {
+                roleMatcher.registerPreferenceListeners();
                 roleMatcher.initFromPreferences();
-                Config.getPref().addPreferenceChangeListener(roleMatcher);
             }
         }
         return roleMatcher;
diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java
index f17f7cf7a..c24b31d97 100644
--- a/src/org/openstreetmap/josm/gui/MainMenu.java
+++ b/src/org/openstreetmap/josm/gui/MainMenu.java
@@ -115,6 +115,7 @@
 import org.openstreetmap.josm.actions.audio.AudioSlowerAction;
 import org.openstreetmap.josm.actions.search.SearchAction;
 import org.openstreetmap.josm.data.UndoRedoHandler;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.dialogs.MenuItemSearchDialog;
 import org.openstreetmap.josm.gui.io.RecentlyOpenedFilesMenu;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
@@ -123,7 +124,6 @@
 import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchPrimitiveDialog;
-import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.PlatformManager;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -814,15 +814,11 @@ public void initialize() {
                 MainMenu.WINDOW_MENU_GROUP.ALWAYS);
         changesetManager.addButtonModel(mi.getModel());
 
-        if (!Config.getPref().getBoolean("audio.menuinvisible", false)) {
+        final BooleanProperty audioMenuInvisible = new BooleanProperty("audio.menuinvisible", false);
+        if (!audioMenuInvisible.get()) {
             showAudioMenu(true);
         }
-
-        Config.getPref().addPreferenceChangeListener(e -> {
-            if ("audio.menuinvisible".equals(e.getKey())) {
-                showAudioMenu(!Boolean.parseBoolean(e.getNewValue().toString()));
-            }
-        });
+        audioMenuInvisible.addListener(event -> showAudioMenu(!event.getProperty().get()));
 
         add(helpMenu, new MenuItemSearchDialog.Action());
         helpMenu.addSeparator();
diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
index a51bf3758..4d8de2b37 100644
--- a/src/org/openstreetmap/josm/gui/MapFrame.java
+++ b/src/org/openstreetmap/josm/gui/MapFrame.java
@@ -52,6 +52,7 @@
 import org.openstreetmap.josm.actions.mapmode.SelectAction;
 import org.openstreetmap.josm.actions.mapmode.ZoomAction;
 import org.openstreetmap.josm.data.ViewportData;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.gui.dialogs.ChangesetDialog;
@@ -78,7 +79,6 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.gui.util.AdvancedKeyPressDetector;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -345,7 +345,7 @@ public void destroy() {
         MainApplication.getLayerManager().removeLayerChangeListener(this);
         MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
         dialogsPanel.destroy();
-        Config.getPref().removePreferenceChangeListener(sidetoolbarPreferencesChangedListener);
+        removeSideToolbarPreferencesChangedListener.run();
         for (int i = 0; i < toolBarActions.getComponentCount(); ++i) {
             if (toolBarActions.getComponent(i) instanceof Destroyable) {
                 ((Destroyable) toolBarActions.getComponent(i)).destroy();
@@ -547,13 +547,12 @@ public void fillPanel(Container panel) {
             final ScrollViewport svp = new ScrollViewport(sideToolBar, ScrollViewport.VERTICAL_DIRECTION);
             sideToolBar = svp;
         }
-        sideToolBar.setVisible(Config.getPref().getBoolean("sidetoolbar.visible", true));
-        sidetoolbarPreferencesChangedListener = e -> {
-            if ("sidetoolbar.visible".equals(e.getKey())) {
-                sideToolBar.setVisible(Config.getPref().getBoolean("sidetoolbar.visible"));
-            }
-        };
-        Config.getPref().addPreferenceChangeListener(sidetoolbarPreferencesChangedListener);
+        final BooleanProperty sideToolbarVisible = new BooleanProperty("sidetoolbar.visible", true);
+        sideToolBar.setVisible(sideToolbarVisible.get());
+        final AbstractProperty.ValueChangeListener<Boolean> sideToolbarPreferencesChangedListener = event ->
+                sideToolBar.setVisible(event.getProperty().get());
+        sideToolbarVisible.addListener(sideToolbarPreferencesChangedListener);
+        removeSideToolbarPreferencesChangedListener = () -> sideToolbarVisible.removeListener(sideToolbarPreferencesChangedListener);
 
         /**
          * sideToolBar: add it to the panel
@@ -796,7 +795,7 @@ public void addTopPanel(Component c) {
      */
     private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<>();
 
-    private transient PreferenceChangedListener sidetoolbarPreferencesChangedListener;
+    private transient Runnable removeSideToolbarPreferencesChangedListener;
     /**
      * Adds a mapMode change listener
      *
diff --git a/src/org/openstreetmap/josm/gui/MapMover.java b/src/org/openstreetmap/josm/gui/MapMover.java
index e293734b7..8420d9ea1 100644
--- a/src/org/openstreetmap/josm/gui/MapMover.java
+++ b/src/org/openstreetmap/josm/gui/MapMover.java
@@ -21,9 +21,6 @@
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.Pair;
 import org.openstreetmap.josm.tools.PlatformManager;
@@ -43,26 +40,12 @@
     public static final BooleanProperty PROP_ZOOM_REVERSE_WHEEL = new BooleanProperty("zoom.reverse-wheel", false);
 
     static {
-        new JMapViewerUpdater();
+        PROP_ZOOM_REVERSE_WHEEL.addListener(event -> updateJMapViewer());
+        updateJMapViewer();
     }
 
-    private static class JMapViewerUpdater implements PreferenceChangedListener {
-
-        JMapViewerUpdater() {
-            Config.getPref().addPreferenceChangeListener(this);
-            updateJMapViewer();
-        }
-
-        @Override
-        public void preferenceChanged(PreferenceChangeEvent e) {
-            if (MapMover.PROP_ZOOM_REVERSE_WHEEL.getKey().equals(e.getKey())) {
-                updateJMapViewer();
-            }
-        }
-
-        private static void updateJMapViewer() {
-            JMapViewer.zoomReverseWheel = MapMover.PROP_ZOOM_REVERSE_WHEEL.get();
-        }
+    private static void updateJMapViewer() {
+        JMapViewer.zoomReverseWheel = MapMover.PROP_ZOOM_REVERSE_WHEEL.get();
     }
 
     private final class ZoomerAction extends AbstractAction {
diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
index 9bc799d0b..fcedeaf20 100644
--- a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
+++ b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
@@ -52,9 +52,6 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
 import org.openstreetmap.josm.gui.widgets.OSDLabel;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.tools.Logging;
 
 /**
@@ -63,7 +60,7 @@
  * @since 12400
  */
 public final class AutoFilterManager
-implements ZoomChangeListener, MapModeChangeListener, DataSetListener, PreferenceChangedListener, LayerChangeListener {
+implements ZoomChangeListener, MapModeChangeListener, DataSetListener, LayerChangeListener {
 
     /**
      * Property to determines if the auto filter feature is enabled.
@@ -123,7 +120,7 @@ public static AutoFilterManager getInstance() {
 
     private AutoFilterManager() {
         MapFrame.addMapModeChangeListener(this);
-        Config.getPref().addPreferenceChangeListener(this);
+        registerPropertyListeners();
         NavigatableComponent.addZoomChangeListener(this);
         MainApplication.getLayerManager().addLayerChangeListener(this);
         DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
@@ -397,9 +394,8 @@ private void resetCurrentAutoFilter() {
         }
     }
 
-    @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        if (e.getKey().equals(PROP_AUTO_FILTER_ENABLED.getKey())) {
+    private void registerPropertyListeners() {
+        PROP_AUTO_FILTER_ENABLED.addListener(event -> {
             if (PROP_AUTO_FILTER_ENABLED.get()) {
                 enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
                 updateButtons();
@@ -407,11 +403,12 @@ public void preferenceChanged(PreferenceChangeEvent e) {
                 enableAutoFilterRule((AutoFilterRule) null);
                 resetCurrentAutoFilter();
             }
-        } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())) {
+        });
+        PROP_AUTO_FILTER_RULE.addListener(event -> {
             enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
             resetCurrentAutoFilter();
             updateButtons();
-        }
+        });
     }
 
     @Override
diff --git a/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java b/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
index dc072e29b..8fff76c8f 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
@@ -48,6 +48,7 @@
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.ParametrizedEnumProperty;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -66,8 +67,6 @@
 import org.openstreetmap.josm.gui.util.WindowGeometry.WindowGeometryException;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -78,7 +77,7 @@
  * This class is a toggle dialog that can be turned on and off.
  * @since 8
  */
-public class ToggleDialog extends JPanel implements ShowHideButtonListener, Helpful, AWTEventListener, Destroyable, PreferenceChangedListener {
+public class ToggleDialog extends JPanel implements ShowHideButtonListener, Helpful, AWTEventListener, Destroyable, AbstractProperty.ValueChangeListener<Boolean>  {
 
     /**
      * The button-hiding strategy in toggler dialogs.
@@ -256,7 +255,7 @@ public ToggleDialog(String name, String iconName, String tooltip, Shortcut short
         setBorder(BorderFactory.createEtchedBorder());
 
         MainApplication.redirectToMainContentPane(this);
-        Config.getPref().addPreferenceChangeListener(this);
+        PROP_DYNAMIC_BUTTONS.addListener(this);
 
         registerInWindowMenu();
     }
@@ -467,7 +466,7 @@ public void destroy() {
         } catch (SecurityException e) {
             Logging.log(Logging.LEVEL_ERROR, "Unable to remove AWT event listener", e);
         }
-        Config.getPref().removePreferenceChangeListener(this);
+        PROP_DYNAMIC_BUTTONS.removeListener(this);
         destroyComponents(this, false);
     }
 
@@ -967,10 +966,8 @@ public void eventDispatched(AWTEvent event) {
     }
 
     @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        if (e.getKey().equals(PROP_DYNAMIC_BUTTONS.getKey())) {
-            dynamicButtonsPropertyChanged();
-        }
+    public void valueChanged(AbstractProperty.ValueChangeEvent<? extends Boolean> e) {
+        dynamicButtonsPropertyChanged();
     }
 
     private void dynamicButtonsPropertyChanged() {
diff --git a/src/org/openstreetmap/josm/gui/io/UploadDialog.java b/src/org/openstreetmap/josm/gui/io/UploadDialog.java
index 8221f605e..9c3d6843c 100644
--- a/src/org/openstreetmap/josm/gui/io/UploadDialog.java
+++ b/src/org/openstreetmap/josm/gui/io/UploadDialog.java
@@ -52,9 +52,6 @@
 import org.openstreetmap.josm.io.UploadStrategy;
 import org.openstreetmap.josm.io.UploadStrategySpecification;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
-import org.openstreetmap.josm.spi.preferences.Setting;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageOverlay;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -67,7 +64,7 @@
  * the upload changeset and the strategy for opening/closing a changeset.
  * @since 2025
  */
-public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
+public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener {
     /** the unique instance of the upload dialog */
     private static UploadDialog uploadDialog;
 
@@ -246,7 +243,13 @@ public void actionPerformed(ActionEvent e) {
 
         setMinimumSize(new Dimension(600, 350));
 
-        Config.getPref().addPreferenceChangeListener(this);
+        OsmApi.SERVER_URL_PROPERTY.addWeakListener(event -> {
+            String url = event.getProperty().get();
+            if (url == null) {
+                url = OsmApi.getOsmApi().getBaseUrl();
+            }
+            setTitle(tr("Upload to ''{0}''", url));
+        });
     }
 
     /**
@@ -633,23 +636,6 @@ public void propertyChange(PropertyChangeEvent evt) {
         }
     }
 
-    /* -------------------------------------------------------------------------- */
-    /* Interface PreferenceChangedListener                                        */
-    /* -------------------------------------------------------------------------- */
-    @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
-            return;
-        final Setting<?> newValue = e.getNewValue();
-        final String url;
-        if (newValue == null || newValue.getValue() == null) {
-            url = OsmApi.getOsmApi().getBaseUrl();
-        } else {
-            url = newValue.getValue().toString();
-        }
-        setTitle(tr("Upload to ''{0}''", url));
-    }
-
     private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
         Collection<String> history = Config.getPref().getList(historyKey, def);
         int age = (int) (System.currentTimeMillis() / 1000 - Config.getPref().getInt(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0));
diff --git a/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java b/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java
index d936dd52c..0fc5ee453 100644
--- a/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java
+++ b/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java
@@ -66,6 +66,7 @@
 import org.openstreetmap.josm.actions.ParameterizedActionDecorator;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
@@ -85,6 +86,7 @@
 public class ToolbarPreferences implements PreferenceSettingFactory {
 
     private static final String EMPTY_TOOLBAR_MARKER = "<!-empty-!>";
+    private static final BooleanProperty PROP_TOOLBAR_VISIBLE = new BooleanProperty("toolbar.visible", true);
 
     /**
      * The prefix for imagery toolbar entries.
@@ -1008,11 +1010,7 @@ public ToolbarPreferences() {
             control.setFloatable(false);
             control.setComponentPopupMenu(popupMenu);
         });
-        Config.getPref().addPreferenceChangeListener(e -> {
-            if ("toolbar.visible".equals(e.getKey())) {
-                refreshToolbarControl();
-            }
-        });
+        PROP_TOOLBAR_VISIBLE.addListener(event -> refreshToolbarControl());
     }
 
     private void loadAction(DefaultMutableTreeNode node, MenuElement menu) {
@@ -1194,7 +1192,7 @@ public void refreshToolbarControl() {
             }
         }
 
-        boolean visible = Config.getPref().getBoolean("toolbar.visible", true);
+        boolean visible = PROP_TOOLBAR_VISIBLE.get();
 
         control.setFocusTraversalKeysEnabled(!unregisterTab);
         control.setVisible(visible && control.getComponentCount() != 0);
diff --git a/src/org/openstreetmap/josm/io/OsmApi.java b/src/org/openstreetmap/josm/io/OsmApi.java
index 858d7ed51..7ac4909cc 100644
--- a/src/org/openstreetmap/josm/io/OsmApi.java
+++ b/src/org/openstreetmap/josm/io/OsmApi.java
@@ -30,6 +30,7 @@
 import org.openstreetmap.josm.data.osm.IPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.io.Capabilities.CapabilitiesParser;
@@ -78,6 +79,9 @@
     @Deprecated
     public static final String DEFAULT_API_URL = "https://api.openstreetmap.org/api";
 
+    public static StringProperty SERVER_URL_PROPERTY = new StringProperty(
+            "osm-server.url", Config.getUrls().getDefaultOsmApiUrl());
+
     // The collection of instantiated OSM APIs
     private static final Map<String, OsmApi> instances = new HashMap<>();
 
@@ -136,17 +140,13 @@ protected static void cacheInstance(OsmApi api) {
         instances.put(api.getServerUrl(), api);
     }
 
-    private static String getServerUrlFromPref() {
-        return Config.getPref().get("osm-server.url", Config.getUrls().getDefaultOsmApiUrl());
-    }
-
     /**
      * Replies the {@link OsmApi} for the URL given by the preference <code>osm-server.url</code>
      *
      * @return the OsmApi
      */
     public static OsmApi getOsmApi() {
-        return getOsmApi(getServerUrlFromPref());
+        return getOsmApi(SERVER_URL_PROPERTY.get());
     }
 
     /** Server URL */
@@ -212,7 +212,8 @@ public String getHost() {
 
         @Override
         protected void checkOfflineAccess() {
-            OnlineResource.OSM_API.checkOfflineAccess(getBaseUrl(getServerUrlFromPref(), "0.6")+CAPABILITIES, getServerUrlFromPref());
+            final String serverUrl = SERVER_URL_PROPERTY.get();
+            OnlineResource.OSM_API.checkOfflineAccess(getBaseUrl(serverUrl, "0.6")+CAPABILITIES, serverUrl);
         }
 
         @Override
