RFR: 8301302: Platform preferences API [v3]

Michael Strauß mstrauss at openjdk.org
Mon Jul 24 17:45:37 UTC 2023


On Sun, 9 Apr 2023 20:23:55 GMT, John Hendrikx <jhendrikx at openjdk.org> wrote:

> With the preferences Map, this could work similar perhaps; set a key to whatever you want with put, and restore it to its original value by setting it to null.

That's how the current API works, with a little bit of added complexity:


interface Preferences {
    ...

    /**
     * Overrides the value of the {@link #appearanceProperty() appearance} property.
     * <p>
     * Specifying {@code null} clears the override, which restores the value of the
     * {@code appearance} property to the platform-provided value.
     * <p>
     * Calling this method does not update the {@code appearance} property instantaneously;
     * instead, the property is only updated after calling {@link #commit()}, or after the
     * occurrence of an operating system event that causes the {@code appearance} property
     * to be recomputed.
     *
     * @param appearance the platform appearance override, or {@code null} to clear the override
     */
    void setAppearance(Appearance appearance);

    ...

    /**
     * Overrides a key-value mapping.
     * <p>
     * If a platform-provided mapping for the key already exists, calling this method overrides
     * the value that is mapped to the key. If a platform-provided mapping for the key doesn't
     * exist, this method creates a new mapping.
     * <p>
     * Specifying a {@code null} value clears the override, which restores the value mapped to
     * the key to the platform-provided value. If the platform does not provide a mapping for
     * the specified key, the mapping is effectively removed.
     * <p>
     * Calling this method does not update the mapping instantaneously; instead, the mapping
     * is only updated after calling {@link #commit()}, or after the occurrence of an operating
     * system event that causes the mapped value to be recomputed.
     *
     * @param key the key
     * @param value the new value, or {@code null} to clear the override
     * @throws NullPointerException if {@code key} is null
     * @throws IllegalArgumentException if a platform-provided mapping for the key exists, and
     *                                  the specified value is an instance of a different class
     *                                  than the platform-provided value
     * @return the previous value associated with {@code key}
     */
    <T> T override(String key, T value);

    /**
     * Commits outstanding overridden preferences, which also causes the values of derived
     * properties to be recomputed.
     */
    void commit();
}


It is very likely the case that changing preferences can lead to very expensive operations in large real-world applications. For example, style themes or the entire user interface may be recreated, icons/images may be loaded, etc.

The `Preferences` implementation accounts for this by firing only a single invalidation notification, even when multiple platform preferences have changed. The documentation of `Preferences` contains guidance for users of this API:

     * @implNote In many cases, multiple platform preferences can change at the same time.
     *           For example, switching from light to dark mode changes various foreground elements to light
     *           colors, and various background elements to dark colors.
     *           <p>
     *           The {@code Preferences} implementation returned from this method fires a single invalidation
     *           event for such bulk changes. If a listener performs potentially heavy work, such as recreating
     *           and applying CSS themes, it might be beneficial to use {@link javafx.beans.InvalidationListener}
     *           instead of {@link javafx.collections.MapChangeListener} to prevent the listener from firing
     *           multiple times in bulk change scenarios.


This works when the changes originate from the OS, but it doesn't work when an application overrides preference mappings manually. `ObservableMap` doesn't support bulk changes, so repeatedly calling `override(...)` would end up firing multiple change notifications, and subscribers would have no way of knowing when the bulk change transaction has ended.

That's where the concept of _uncommitted modifications_ comes into play: calling `override(...)` or any of the property setters like `setAppearance(...)` doesn't apply the changes immediately, it delays those changes until `commit()` is called or until an OS event causes the preference to be recomputed. When that happens, a single invalidation notification is fired, the same as it would have if the change originated from the OS.

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1014#issuecomment-1502081527


More information about the openjfx-dev mailing list