On Sun, 9 Apr 2023 20:23:55 GMT, John Hendrikx <jhendr...@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

Reply via email to