diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java b/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java index ccabc41808e..8f1812ae445 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -93,10 +93,8 @@ public StringProperty skinClassNameProperty(Control control) { } }); - // Ensures that the default application user agent stylesheet is loaded - if (Application.getUserAgentStylesheet() == null) { - PlatformImpl.setDefaultPlatformUserAgentStylesheet(); - } + // Ensures that the default theme is loaded + PlatformImpl.ensureDefaultTheme(); } /** diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/PopupControl.java b/modules/javafx.controls/src/main/java/javafx/scene/control/PopupControl.java index 0fad9032141..98bb3af16cd 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/PopupControl.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/PopupControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,10 +84,8 @@ public class PopupControl extends PopupWindow implements Skinnable, Styleable { public static final double USE_COMPUTED_SIZE = -1; static { - // Ensures that the default application user agent stylesheet is loaded - if (Application.getUserAgentStylesheet() == null) { - PlatformImpl.setDefaultPlatformUserAgentStylesheet(); - } + // Ensures that the default theme is loaded + PlatformImpl.ensureDefaultTheme(); } /** diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/theme/CaspianTheme.java b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/CaspianTheme.java new file mode 100644 index 00000000000..3f34430d1fa --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/CaspianTheme.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.theme; + +import com.sun.javafx.PlatformUtil; +import com.sun.javafx.application.PlatformImpl; +import javafx.application.ConditionalFeature; +import javafx.application.Platform; +import javafx.beans.value.WritableValue; + +/** + * {@code Caspian} is a built-in JavaFX theme that shipped as default in JavaFX 2. + * + * @since 21 + */ +public class CaspianTheme extends ThemeBase { + + private final WritableValue highContrastStylesheet; + + /** + * Creates a new instance of the {@code CaspianTheme} class. + */ + public CaspianTheme() { + addLast("com/sun/javafx/scene/control/skin/caspian/caspian.css"); + + if (PlatformImpl.isSupported(ConditionalFeature.INPUT_TOUCH)) { + addLast("com/sun/javafx/scene/control/skin/caspian/embedded.css"); + + if (com.sun.javafx.util.Utils.isQVGAScreen()) { + addLast("com/sun/javafx/scene/control/skin/caspian/embedded-qvga.css"); + } + + if (PlatformUtil.isAndroid()) { + addLast("com/sun/javafx/scene/control/skin/caspian/android.css"); + } + + if (PlatformUtil.isIOS()) { + addLast("com/sun/javafx/scene/control/skin/caspian/ios.css"); + } + } + + if (PlatformImpl.isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { + addLast("com/sun/javafx/scene/control/skin/caspian/two-level-focus.css"); + } + + if (PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { + addLast("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); + } + + if (!PlatformImpl.isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) { + addLast("com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css"); + } + + highContrastStylesheet = addLast(null); + updateHighContrastTheme(); + } + + @Override + protected void onPreferencesChanged() { + updateHighContrastTheme(); + } + + private void updateHighContrastTheme() { + boolean enabled = false; + String overrideThemeName = System.getProperty("com.sun.javafx.highContrastTheme"); + if (overrideThemeName != null) { + enabled = true; + } + + if (!enabled) { + Platform.Preferences preferences = Platform.getPreferences(); + if (preferences.getBoolean("Windows.SPI.HighContrastOn", false)) { + enabled = preferences.getString("Windows.SPI.HighContrastColorScheme") != null; + } + } + + if (enabled) { + // caspian has only one high contrast theme, use it regardless of the user or platform theme. + highContrastStylesheet.setValue("com/sun/javafx/scene/control/skin/caspian/highcontrast.css"); + } else { + highContrastStylesheet.setValue(null); + } + } + +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ModenaTheme.java b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ModenaTheme.java new file mode 100644 index 00000000000..3caaf172b2b --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ModenaTheme.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.theme; + +import com.sun.javafx.PlatformUtil; +import com.sun.javafx.application.HighContrastScheme; +import com.sun.javafx.application.PlatformImpl; +import javafx.application.ConditionalFeature; +import javafx.application.Platform; +import javafx.beans.value.WritableValue; +import java.util.ResourceBundle; + +/** + * {@code Modena} is the default built-in JavaFX theme as of JavaFX 8. + * + * @since 21 + */ +public class ModenaTheme extends ThemeBase { + + private final WritableValue highContrastStylesheet; + + /** + * Creates a new instance of the {@code ModenaTheme} class. + */ + public ModenaTheme() { + addLast("com/sun/javafx/scene/control/skin/modena/modena.css"); + + if (PlatformImpl.isSupported(ConditionalFeature.INPUT_TOUCH)) { + addLast("com/sun/javafx/scene/control/skin/modena/touch.css"); + } + + if (PlatformUtil.isEmbedded()) { + addLast("com/sun/javafx/scene/control/skin/modena/modena-embedded-performance.css"); + } + + if (PlatformUtil.isAndroid()) { + addLast("com/sun/javafx/scene/control/skin/modena/android.css"); + } + + if (PlatformUtil.isIOS()) { + addLast("com/sun/javafx/scene/control/skin/modena/ios.css"); + } + + if (PlatformImpl.isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { + addLast("com/sun/javafx/scene/control/skin/modena/two-level-focus.css"); + } + + if (PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { + addLast("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); + } + + if (!PlatformImpl.isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) { + addLast("com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css"); + } + + highContrastStylesheet = addLast(null); + updateHighContrastTheme(); + } + + @Override + protected void onPreferencesChanged() { + updateHighContrastTheme(); + } + + private void updateHighContrastTheme() { + String themeName = null; + String overrideThemeName = System.getProperty("com.sun.javafx.highContrastTheme"); + if (overrideThemeName != null) { + themeName = overrideThemeName; + } + + if (themeName == null) { + Platform.Preferences preferences = Platform.getPreferences(); + if (preferences.getBoolean("Windows.SPI.HighContrastOn", false)) { + themeName = preferences.getString("Windows.SPI.HighContrastColorScheme"); + } + } + + if (themeName != null) { + String stylesheet = switch (themeName.toUpperCase()) { + case "BLACKONWHITE" -> "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css"; + case "WHITEONBLACK" -> "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css"; + case "YELLOWONBLACK" -> "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css"; + default -> null; + }; + + if (stylesheet == null) { + ResourceBundle bundle = ResourceBundle.getBundle("com/sun/glass/ui/win/themes"); + String enumValue = HighContrastScheme.fromThemeName(bundle::getString, themeName); + + stylesheet = enumValue != null ? switch (HighContrastScheme.valueOf(enumValue)) { + case HIGH_CONTRAST_WHITE -> "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css"; + case HIGH_CONTRAST_BLACK -> "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css"; + case HIGH_CONTRAST_1, HIGH_CONTRAST_2 -> "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css"; + } : null; + } + + highContrastStylesheet.setValue(stylesheet); + } else { + highContrastStylesheet.setValue(null); + } + } + +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/theme/StylesheetList.java b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/StylesheetList.java new file mode 100644 index 00000000000..8aae67cf4a7 --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/StylesheetList.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.theme; + +import javafx.beans.value.WritableValue; +import javafx.collections.ObservableListBase; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Implements a list that allows individual elements to be changed via a {@link WritableValue} wrapper. + */ +class StylesheetList extends ObservableListBase { + + private final List> elements; + private final List values; + + public StylesheetList() { + elements = new ArrayList<>(); + values = new ArrayList<>(); + } + + public void lock() { + beginChange(); + } + + public void unlock() { + endChange(); + } + + public WritableValue addFirstElement(String value) { + if (value != null) { + values.add(0, value); + } + + WritableValue element = new ElementImpl(value); + elements.add(0, element); + return element; + } + + public WritableValue addLastElement(String value) { + if (value != null) { + values.add(value); + } + + WritableValue element = new ElementImpl(value); + elements.add(element); + return element; + } + + @Override + public String get(int index) { + return values.get(index); + } + + @Override + public int size() { + return values.size(); + } + + private class ElementImpl implements WritableValue { + String currentValue; + + ElementImpl(String initialValue) { + currentValue = initialValue; + } + + @Override + public String getValue() { + return currentValue; + } + + @Override + public void setValue(String newValue) { + if (Objects.equals(currentValue, newValue)) { + return; + } + + int index = 0; + for (int i = 0, max = elements.size(); i < max; ++i) { + WritableValue element = elements.get(i); + if (element == this) { + break; + } else if (element.getValue() != null) { + ++index; + } + } + + beginChange(); + + if (currentValue == null && newValue != null) { + nextAdd(index, index + 1); + values.add(index, newValue); + } else if (currentValue != null && newValue == null) { + nextRemove(index, currentValue); + values.remove(index); + } else if (currentValue != null) { + nextReplace(index, index + 1, List.of(currentValue)); + values.set(index, newValue); + } + + currentValue = newValue; + + endChange(); + } + } + +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ThemeBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ThemeBase.java new file mode 100644 index 00000000000..e1a4adb6458 --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/ThemeBase.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.theme; + +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.value.WritableValue; +import javafx.collections.ObservableList; +import javafx.css.StyleTheme; + +/** + * {@link ThemeBase} is a {@link StyleTheme} implementation that simplifies toggling or modifying + * stylesheets while retaining the order in which the stylesheets were originally added. + *

+ * Stylesheet URIs can be added to the theme by calling {@link #addFirst(String)} or {@link #addLast(String)}. + * The value of a stylesheet URI can be changed at any time with the {@link WritableValue} wrapper + * that is returned by {@code addFirst} and {@code addLast}. + * + * @since 21 + */ +public abstract class ThemeBase implements StyleTheme { + + private final StylesheetList stylesheetList = new StylesheetList(); + + private final InvalidationListener preferencesChanged = observable -> { + try { + stylesheetList.lock(); + onPreferencesChanged(); + } finally { + stylesheetList.unlock(); + } + }; + + /** + * Creates a new instance of the {@code ThemeBase} class. + */ + protected ThemeBase() { + Platform.getPreferences().addListener(new WeakInvalidationListener(preferencesChanged)); + } + + @Override + public final ObservableList getStylesheets() { + return stylesheetList; + } + + /** + * Adds a new stylesheet URL at the front of the list of stylesheets. + *

+ * The returned {@link WritableValue} can be used to change the value of the URL at runtime. + * If the value is set to {@code null}, the stylesheet will not be included in the CSS cascade. + * + * @param url the stylesheet URL, or {@code null} + * @return a {@code WritableValue} that represents the stylesheet URL in the list of stylesheets + */ + protected final WritableValue addFirst(String url) { + return stylesheetList.addFirstElement(url); + } + + /** + * Adds a new stylesheet URL at the back of the list of stylesheets. + *

+ * The returned {@link WritableValue} can be used to change the value of the URL at runtime. + * If the value is set to {@code null}, the stylesheet will not be included in the CSS cascade. + * + * @param url the stylesheet URL, or {@code null} + * @return a {@code WritableValue} that represents the stylesheet URL in the list of stylesheets + */ + protected final WritableValue addLast(String url) { + return stylesheetList.addLastElement(url); + } + + /** + * Occurs when platform preferences have changed. + */ + protected void onPreferencesChanged() {} + +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/theme/package.html b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/package.html new file mode 100644 index 00000000000..c2b081a6b78 --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/theme/package.html @@ -0,0 +1,54 @@ + + + + + + + +javafx.scene.control.theme + + +

This package contains the built-in style themes for JavaFX controls.

+
    +
  1. {@link javafx.scene.control.theme.CaspianTheme Caspian}, which is the default theme for JavaFX 2.X
  2. +
  3. {@link javafx.scene.control.theme.ModenaTheme Modena}, which is the default theme for JavaFX 8.X and later
  4. +
+

Style themes can be applied using the {@link javafx.application.Application#setUserAgentStyleTheme} method:

+
{@code
+    public class App extends Application {
+        @Override
+        public void start(Stage primaryStage) {
+            setUserAgentStyleTheme(new CaspianTheme());
+
+            primaryStage.setScene(...);
+            primaryStage.show();
+        }
+    }
+}
+ + diff --git a/modules/javafx.controls/src/main/java/module-info.java b/modules/javafx.controls/src/main/java/module-info.java index eb079a6e7e5..ba651c21fda 100644 --- a/modules/javafx.controls/src/main/java/module-info.java +++ b/modules/javafx.controls/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,6 +38,7 @@ exports javafx.scene.control; exports javafx.scene.control.cell; exports javafx.scene.control.skin; + exports javafx.scene.control.theme; exports com.sun.javafx.scene.control to javafx.web; diff --git a/modules/javafx.controls/src/shims/java/javafx/scene/control/theme/StylesheetListShim.java b/modules/javafx.controls/src/shims/java/javafx/scene/control/theme/StylesheetListShim.java new file mode 100644 index 00000000000..4e6c5de07d1 --- /dev/null +++ b/modules/javafx.controls/src/shims/java/javafx/scene/control/theme/StylesheetListShim.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.theme; + +public class StylesheetListShim extends StylesheetList { +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/BuiltinThemeTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/BuiltinThemeTest.java new file mode 100644 index 00000000000..f91f3fb5eef --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/BuiltinThemeTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.theme; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import javafx.css.StyleTheme; +import javafx.scene.control.theme.CaspianTheme; +import javafx.scene.control.theme.ModenaTheme; + +import static com.sun.javafx.application.PlatformImpl.*; +import static org.junit.jupiter.api.Assertions.*; + +public class BuiltinThemeTest { + + private static String originalUAStylesheet; + private static StyleTheme originalTheme; + + @BeforeAll + static void beforeAll() { + originalUAStylesheet = platformUserAgentStylesheetProperty().get(); + originalTheme = platformUserAgentStyleThemeProperty().get(); + } + + @AfterAll + static void afterAll() { + platformUserAgentStyleThemeProperty().set(originalTheme); + platformUserAgentStylesheetProperty().set(originalUAStylesheet); + } + + /* + When platformUserAgentStylesheet is set to a built-in theme name, platformTheme is implicitly + set to the corresponding theme class: + + ┌───────────────────────────────────────────────────────────────┐ + │ platformUserAgentStylesheet platformTheme │ + ├───────────────────────────────────────────────────────────────┤ + │ null --> foo.css null │ + │ foo.css --> CASPIAN null --> CaspianTheme │ + │ CASPIAN --> MODENA CaspianTheme --> ModenaTheme │ + └───────────────────────────────────────────────────────────────┘ + */ + @Test + public void testThemeIsImplicitlySet() { + platformUserAgentStylesheetProperty().set(null); + platformUserAgentStyleThemeProperty().set(null); + + platformUserAgentStylesheetProperty().set("foo.css"); + assertNull(platformUserAgentStyleThemeProperty().get()); + + platformUserAgentStylesheetProperty().set("CASPIAN"); + assertEquals("CaspianTheme", platformUserAgentStyleThemeProperty().get().getClass().getSimpleName()); + + platformUserAgentStylesheetProperty().set("MODENA"); + assertEquals("ModenaTheme", platformUserAgentStyleThemeProperty().get().getClass().getSimpleName()); + } + + /* + When platformTheme is explicitly set to one of the built-in themes, platformUserAgentStylesheet + is cleared when it was previously set to one of the built-in theme constants. + */ + @Test + public void testUAConstantIsClearedWhenBuiltinThemeIsExplicitlySet() { + platformUserAgentStylesheetProperty().set("CASPIAN"); + platformUserAgentStyleThemeProperty().set(new CaspianTheme()); + assertNull(platformUserAgentStylesheetProperty().get()); + assertTrue(platformUserAgentStyleThemeProperty().get() instanceof CaspianTheme); + + platformUserAgentStylesheetProperty().set("MODENA"); + platformUserAgentStyleThemeProperty().set(new ModenaTheme()); + assertNull(platformUserAgentStylesheetProperty().get()); + assertTrue(platformUserAgentStyleThemeProperty().get() instanceof ModenaTheme); + } + + /* + When platformTheme is explicitly set to one of the built-in themes, platformUserAgentStylesheet + is NOT cleared when its value does not represent a built-in theme constant. + */ + @Test + public void testUAStylesheetIsNotClearedWhenBuiltinThemeIsExplicitlySet() { + platformUserAgentStylesheetProperty().set("foo.css"); + platformUserAgentStyleThemeProperty().set(new CaspianTheme()); + assertEquals("foo.css", platformUserAgentStylesheetProperty().get()); + assertTrue(platformUserAgentStyleThemeProperty().get() instanceof CaspianTheme); + } + +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/CaspianThemeTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/CaspianThemeTest.java new file mode 100644 index 00000000000..f9b07da3569 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/CaspianThemeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.theme; + +import com.sun.javafx.application.PlatformImpl; +import com.sun.javafx.application.PlatformPreferencesImpl; +import org.junit.jupiter.api.Test; +import javafx.scene.control.theme.CaspianTheme; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class CaspianThemeTest { + + @Test + public void testHighContrastThemeWithSystemProperty() { + var theme = new CaspianTheme(); + assertFalse(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("highcontrast.css"))); + System.setProperty("com.sun.javafx.highContrastTheme", "ANY_VALUE_HERE"); + theme = new CaspianTheme(); + assertTrue(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("highcontrast.css"))); + System.clearProperty("com.sun.javafx.highContrastTheme"); + } + + @Test + public void testHighContrastThemeWithPlatformPreference() { + var theme = new CaspianTheme(); + assertFalse(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("highcontrast.css"))); + + Map map = ((PlatformPreferencesImpl)PlatformImpl.getPlatformPreferences()).getModifiableMap(); + Object originalOn = map.put("Windows.SPI.HighContrastOn", true); + Object originalName = map.put("Windows.SPI.HighContrastColorScheme", "ANY_VALUE_HERE"); + + theme = new CaspianTheme(); + assertTrue(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("highcontrast.css"))); + + map.put("Windows.SPI.HighContrastOn", originalOn); + map.put("Windows.SPI.HighContrastColorScheme", originalName); + } + +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ModenaThemeTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ModenaThemeTest.java new file mode 100644 index 00000000000..c1add6c87d1 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ModenaThemeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.theme; + +import com.sun.javafx.application.PlatformImpl; +import com.sun.javafx.application.PlatformPreferencesImpl; +import org.junit.jupiter.api.Test; +import javafx.scene.control.theme.ModenaTheme; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class ModenaThemeTest { + + @Test + public void testHighContrastThemeWithSystemProperty() { + var theme = new ModenaTheme(); + assertFalse(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("blackOnWhite.css"))); + System.setProperty("com.sun.javafx.highContrastTheme", "BLACKONWHITE"); + theme = new ModenaTheme(); + assertTrue(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("blackOnWhite.css"))); + System.clearProperty("com.sun.javafx.highContrastTheme"); + } + + @Test + public void testHighContrastThemeWithPlatformPreference() { + var theme = new ModenaTheme(); + assertFalse(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("blackOnWhite.css"))); + + Map map = ((PlatformPreferencesImpl)PlatformImpl.getPlatformPreferences()).getModifiableMap(); + Object originalOn = map.put("Windows.SPI.HighContrastOn", true); + Object originalName = map.put("Windows.SPI.HighContrastColorScheme", "BLACKONWHITE"); + + theme = new ModenaTheme(); + assertTrue(theme.getStylesheets().stream().anyMatch(fileName -> fileName.contains("blackOnWhite.css"))); + + map.put("Windows.SPI.HighContrastOn", originalOn); + map.put("Windows.SPI.HighContrastColorScheme", originalName); + } + +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/StylesheetListTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/StylesheetListTest.java new file mode 100644 index 00000000000..a4e07938f36 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/StylesheetListTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.theme; + +import javafx.scene.control.theme.StylesheetListShim; +import org.junit.jupiter.api.Test; +import test.javafx.collections.MockListObserver; +import javafx.beans.value.WritableValue; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class StylesheetListTest { + + @Test + public void testEmptyList() { + var list = new StylesheetListShim(); + list.addLastElement(null); + list.addLastElement(null); + list.addLastElement(null); + + assertEquals(0, list.size()); + } + + @Test + public void testAddLast() { + var list = new StylesheetListShim(); + list.addLastElement("foo"); + list.addLastElement("bar"); + list.addLastElement("baz"); + + assertEquals(List.of("foo", "bar", "baz"), list); + } + + @Test + public void testAddFirst() { + var list = new StylesheetListShim(); + list.addLastElement("foo"); + list.addFirstElement("bar"); + list.addFirstElement("baz"); + + assertEquals(List.of("baz", "bar", "foo"), list); + } + + @Test + public void testToggleItem() { + var list = new StylesheetListShim() { + final WritableValue p1 = addLastElement(null); + final WritableValue p2 = addLastElement(null); + final WritableValue p3 = addLastElement(null); + }; + + list.p1.setValue("foo"); + assertEquals(List.of("foo"), list); + + list.p3.setValue("bar"); + assertEquals(List.of("foo", "bar"), list); + + list.p1.setValue(null); + assertEquals(List.of("bar"), list); + } + + @Test + public void testChangeItem() { + var list = new StylesheetListShim() { + final WritableValue p1 = addLastElement(null); + final WritableValue p2 = addLastElement(null); + final WritableValue p3 = addLastElement(null); + }; + + list.p1.setValue("foo"); + assertEquals(List.of("foo"), list); + + list.p3.setValue("bar"); + assertEquals(List.of("foo", "bar"), list); + + list.p3.setValue("baz"); + assertEquals(List.of("foo", "baz"), list); + } + + @Test + public void testChangeEvent() { + var list = new StylesheetListShim() { + final WritableValue p1 = addLastElement(null); + final WritableValue p2 = addLastElement(null); + final WritableValue p3 = addLastElement(null); + }; + + var observer = new MockListObserver(); + list.addListener(observer); + + list.p1.setValue("foo"); + observer.check1AddRemove(list, List.of(), 0, 1); + observer.clear(); + + list.p3.setValue("bar"); + observer.check1AddRemove(list, List.of(), 1, 2); + observer.clear(); + + list.p2.setValue("baz"); + observer.check1AddRemove(list, List.of(), 1, 2); + observer.clear(); + + list.p2.setValue("qux"); + observer.check1AddRemove(list, List.of("baz"), 1, 2); + observer.clear(); + + list.p2.setValue(null); + observer.check1AddRemove(list, List.of("qux"), 1, 1); + observer.clear(); + + list.p3.setValue(null); + observer.check1AddRemove(list, List.of("bar"), 1, 1); + observer.clear(); + } + + @Test + public void testBatchChange() { + var list = new StylesheetListShim() { + final WritableValue p1 = addLastElement(null); + final WritableValue p2 = addLastElement(null); + final WritableValue p3 = addLastElement("baz"); + }; + + var observer = new MockListObserver(); + list.addListener(observer); + + list.lock(); + list.p1.setValue("foo"); + observer.check0(); + + list.p2.setValue("bar"); + observer.check0(); + + list.p3.setValue(null); + observer.check0(); + + list.unlock(); + observer.check1(); + observer.check1AddRemove(list, List.of("baz"), 0, 2); + + } + +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ThemeBaseTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ThemeBaseTest.java new file mode 100644 index 00000000000..968349667cf --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/theme/ThemeBaseTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.theme; + +import com.sun.javafx.application.PlatformImpl; +import org.junit.jupiter.api.Test; +import javafx.scene.control.theme.ThemeBase; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class ThemeBaseTest { + + @Test + public void testOnPreferencesChangedIsInvokedWhenPreferencesAreInvalidated() { + int[] count = new int[1]; + var theme = new ThemeBase() { + @Override + protected void onPreferencesChanged() { + count[0]++; + } + }; + + PlatformImpl.updatePreferences(Map.of("foo", "bar")); + assertEquals(1, count[0]); + + PlatformImpl.updatePreferences(Map.of("foo", "baz", "qux", "quz")); + assertEquals(2, count[0]); + } + + @Test + public void testAddFirst() { + var theme = new ThemeBase() { + { + addFirst("foo"); + addFirst("bar"); + } + }; + + assertEquals(List.of("bar", "foo"), theme.getStylesheets()); + } + + @Test + public void testAddLast() { + var theme = new ThemeBase() { + { + addLast("foo"); + addLast("bar"); + } + }; + + assertEquals(List.of("foo", "bar"), theme.getStylesheets()); + } + +} diff --git a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html index 41b53593f28..7b96d776884 100644 --- a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html +++ b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html @@ -2,7 +2,7 @@