RFR: 8301302: Platform preferences API [v3]

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


On Thu, 2 Feb 2023 19:54:33 GMT, Michael Strauß <mstrauss at openjdk.org> wrote:

>> Please read [this document](https://gist.github.com/mstr2/9f46f92c98d3c86aa6a0b4224a9a6548) for an introduction to the Platform Preferences API, and how it interacts with the proposed style theme and stage appearance features.
>
> Michael Strauß has updated the pull request with a new target base due to a merge or a rebase.

In general, platform preferences correspond to OS-level settings and are updated dynamically. Third-party themes might integrate platform preferences into their look and feel, which is often what users expect to see. But consider a scenario where an application uses a third-party theme that adapts to the OS appearance, but the application author only wants to support a dark appearance (independent from the OS appearance).

For this scenario, platform preferences should be overridable from application code. I've considered several potential approaches:

#### 1. Provide two sets of preferences:

class Platform {
    Preferences getUserPreferences();
    Preferences getPlatformPreferences();
}

In this idea, `getUserPreferences` returns a mutable version of the immutable `getPlatformPreferences`. However, this requires theme authors to always use user preferences instead of platform preferences (or combine both); if they fail to do so, application authors are out of luck.

#### 2. Add a separate, overridable property for each of the convenience API properties:

interface Preferences {
    ...
    ReadOnlyObjectProperty<Appearance> appearanceProperty();
    ObjectProperty<Appearance> appearanceOverrideProperty();
    ...
}

The value of the read-only `appearanceProperty()` then corresponds to the value of `appearanceOverrideProperty()` if the property is set to a non-null value. Otherwise, it corresponds to the platform-provided value.

#### 3. Add a special setter for each of the convenience API properties:

interface Preferences {
    ...
    ReadOnlyObjectProperty<Appearance> appearanceProperty();
    void setAppearance(Appearance appearance);
    ...
}

The `setAppearance` method can be used to _override_ the value of `appearanceProperty()`. When `null` is passed into this method, the platform default value is restored (or, put differently, the override is cleared). There is some precedence in JavaFX for a "special setter" pattern: for example, while the `Stage.widthProperty()` is read-only, `Stage.setWidth` is a special setter that attempts to set the stage width to the specified value (and may fail to do so).

I prefer the third option (the special setter), as it seems to be the cleanest approach that doesn't unnecessarily expand the API.

I think I've come around to the idea of dropping the `override` method in favor of `put`. However, I don't like the idea of using `put("key", null)` to reset an overridden mapping to its default value. Having "reset" be its own operation seems to work quite well, though:


interface Preferences extends ObservableMap<String, Object> {
    ...

    /**
     * Overrides a preference 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.
     *
     * @param key the key
     * @param value the new value
     * @throws NullPointerException if {@code key} or {@code value} 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}
     */
    Object put(String key, Object value);

    /**
     * Resets an overridden preference mapping to its platform-provided value.
     * <p>
     * If the preference is overridden, but the platform does not provide a mapping for the
     * specified key, the mapping will be removed. If no mapping exists for the specified
     * key, calling this method has no effect.
     *
     * @param key the key
     * @throws NullPointerException if {@code key} is null
     */
    void reset(String key);

    /**
     * Resets all overridden preference mappings to their platform-provided values and removes
     * all mappings for which the platform does not provide a default value.
     */
    void reset();
}

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

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


More information about the openjfx-dev mailing list