diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/schema/org.eclipse.e4.ui.css.swt.theme.exsd b/bundles/org.eclipse.e4.ui.css.swt.theme/schema/org.eclipse.e4.ui.css.swt.theme.exsd
index 5df13746187..7248acb95ad 100644
--- a/bundles/org.eclipse.e4.ui.css.swt.theme/schema/org.eclipse.e4.ui.css.swt.theme.exsd
+++ b/bundles/org.eclipse.e4.ui.css.swt.theme/schema/org.eclipse.e4.ui.css.swt.theme.exsd
@@ -134,6 +134,13 @@
+
+
+
+ whether this theme is a dark theme. Defaults to <code>false</code>, unless the theme id contains "dark", which is the legacy heuristic used before this attribute existed.
+
+
+
diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/Theme.java b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/Theme.java
index 8fc5326cf3d..a48e0b3e311 100644
--- a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/Theme.java
+++ b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/Theme.java
@@ -18,11 +18,17 @@
public class Theme implements ITheme {
private final String id;
private final String label;
+ private final boolean dark;
private String osVersion;
public Theme(String id, String label) {
+ this(id, label, id != null && id.contains("dark")); //$NON-NLS-1$
+ }
+
+ public Theme(String id, String label, boolean dark) {
this.id = id;
this.label = label;
+ this.dark = dark;
}
@Override
@@ -35,6 +41,11 @@ public String getLabel() {
return label;
}
+ @Override
+ public boolean isDark() {
+ return dark;
+ }
+
public void setOsVersion(String version) {
this.osVersion = version;
}
@@ -45,8 +56,8 @@ public String getOsVersion() {
@Override
public String toString() {
- return "Theme [id=" + id + ", label='" + label + "', osVersion="
- + osVersion + "]";
+ return "Theme [id=" + id + ", label='" + label + "', dark=" + dark
+ + ", osVersion=" + osVersion + "]";
}
diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java
index 27f68c94fb7..57b14178821 100644
--- a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java
+++ b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java
@@ -30,6 +30,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.FileLocator;
@@ -89,6 +90,13 @@ public class ThemeEngine implements IThemeEngine {
private static final String THEMEID_KEY = "themeid";
+ /**
+ * Prefix of the key recording a theme's appearance. The theme id is part of the
+ * key, so a record left behind by another installation sharing the preferences
+ * cannot be mistaken for the appearance of the theme in {@link #THEMEID_KEY}.
+ */
+ private static final String THEME_IS_DARK_KEY_PREFIX = "themeIsDark.";
+
public static final String THEME_PLUGIN_ID = "org.eclipse.e4.ui.css.swt.theme";
public static final String E4_DARK_THEME_ID = "org.eclipse.e4.ui.css.theme.e4_dark";
@@ -157,13 +165,14 @@ public ThemeEngine(Display display) {
final String themeBaseId = id + version;
String themeId = themeBaseId;
String label = ce.getAttribute("label");
+ boolean dark = isDarkTheme(ce);
String originalCSSFile;
String basestylesheeturi = originalCSSFile = ce.getAttribute("basestylesheeturi");
if (!basestylesheeturi.startsWith("platform:/plugin/")) {
basestylesheeturi = "platform:/plugin/" + ce.getContributor().getName() + "/"
+ basestylesheeturi;
}
- registerTheme(themeId, label, basestylesheeturi, version);
+ registerTheme(themeId, label, basestylesheeturi, version, dark);
//check for modified files
if (modifiedFiles != null) {
@@ -237,6 +246,20 @@ public ThemeEngine(Display display) {
}
+ /**
+ * Themes declare their appearance with the {@code isDarkTheme} attribute. Themes
+ * contributed before that attribute existed are classified by their id, which is
+ * the convention the platform relied on so far.
+ */
+ private static boolean isDarkTheme(IConfigurationElement themeElement) {
+ String dark = themeElement.getAttribute("isDarkTheme");
+ if (dark != null) {
+ return Boolean.parseBoolean(dark);
+ }
+ String id = themeElement.getAttribute("id");
+ return id != null && id.contains("dark"); //$NON-NLS-1$
+ }
+
private boolean isOsVersionMatch(String osVersionList) {
boolean found = false;
String osVersion = System.getProperty("os.version");
@@ -264,13 +287,15 @@ public synchronized ITheme registerTheme(String id, String label, String basesty
public synchronized ITheme registerTheme(String id, String label,
String basestylesheetURI, String osVersion) throws IllegalArgumentException {
- for (Theme t : themes) {
- if (t.getId().equals(id)) {
- throw new IllegalArgumentException("A theme with the id '" + id
- + "' is already registered");
- }
+ return registerTheme(id, label, basestylesheetURI, osVersion, id.contains("dark")); //$NON-NLS-1$
+ }
+
+ public synchronized ITheme registerTheme(String id, String label, String basestylesheetURI, String osVersion,
+ boolean dark) throws IllegalArgumentException {
+ if (themes.stream().anyMatch(t -> t.getId().equals(id))) {
+ throw new IllegalArgumentException("A theme with the id '" + id + "' is already registered");
}
- Theme theme = new Theme(id, label);
+ Theme theme = new Theme(id, label, dark);
if (osVersion != "") {
theme.setOsVersion(osVersion);
}
@@ -512,6 +537,7 @@ public void setTheme(ITheme theme, boolean restore, boolean force) {
EclipsePreferencesHelper.setCurrentThemeId(theme.getId());
pref.put(THEMEID_KEY, theme.getId());
+ pref.putBoolean(darkKey(theme.getId()), theme.isDark());
try {
pref.flush();
} catch (BackingStoreException e) {
@@ -520,8 +546,7 @@ public void setTheme(ITheme theme, boolean restore, boolean force) {
}
publishEffectiveThemeId();
- boolean isDark = theme.getId().contains("dark"); //$NON-NLS-1$
- display.setDarkThemePreferred(isDark);
+ display.setDarkThemePreferred(theme.isDark());
sendThemeChangeEvent(restore);
@@ -599,7 +624,9 @@ private String getPreferenceThemeId() {
*/
private void publishEffectiveThemeId() {
if (currentTheme != null) {
- DefaultScope.INSTANCE.getNode(THEME_PLUGIN_ID).put(THEMEID_KEY, currentTheme.getId());
+ IEclipsePreferences defaults = DefaultScope.INSTANCE.getNode(THEME_PLUGIN_ID);
+ defaults.put(THEMEID_KEY, currentTheme.getId());
+ defaults.putBoolean(darkKey(currentTheme.getId()), currentTheme.isDark());
effectiveThemeIdPublished = true;
}
}
@@ -625,24 +652,23 @@ public void restore(String alternateTheme) {
// use theme from preferences if it exists
if (prefThemeId != null) {
- for (ITheme t : getThemes()) {
- if (prefThemeId.equals(t.getId())) {
- setTheme(t, false);
- return;
- }
+ Optional prefTheme = getThemes().stream().filter(t -> prefThemeId.equals(t.getId())).findFirst();
+ if (prefTheme.isPresent()) {
+ setTheme(prefTheme.get(), false);
+ return;
}
}
- boolean hasDarkTheme = getThemes().stream().anyMatch(t -> t.getId().startsWith(E4_DARK_THEME_ID));
+ Optional darkTheme = findDarkTheme();
boolean overrideWithDarkTheme = false;
- if (hasDarkTheme) {
+ if (darkTheme.isPresent()) {
if (prefThemeId != null) {
/*
* The user had previously selected a theme which is not available anymore. In
* this case want to fall back to respect whether that previous choice was dark
* or not. https://github.com/eclipse-platform/eclipse.platform.ui/issues/2776
*/
- overrideWithDarkTheme = prefThemeId.contains("dark");
+ overrideWithDarkTheme = isPreferenceThemeDark(prefThemeId);
} else {
/*
* No previous theme selection in preferences. In this case check if the system
@@ -654,12 +680,43 @@ public void restore(String alternateTheme) {
}
}
- String themeToRestore = overrideWithDarkTheme ? E4_DARK_THEME_ID : alternateTheme;
+ String themeToRestore = overrideWithDarkTheme ? darkTheme.get().getId() : alternateTheme;
if (themeToRestore != null) {
setTheme(themeToRestore, false);
}
}
+ private static String darkKey(String themeId) {
+ return THEME_IS_DARK_KEY_PREFIX + themeId;
+ }
+
+ /**
+ * Whether the theme recorded in the preferences is a dark one. Preferences
+ * written before themes declared their appearance only carry the theme id, so
+ * fall back to the id based classification.
+ */
+ private boolean isPreferenceThemeDark(String prefThemeId) {
+ boolean darkById = prefThemeId.contains("dark"); //$NON-NLS-1$
+ IPreferencesService prefService = Platform.getPreferencesService();
+ if (!effectiveThemeIdPublished) {
+ return prefService.getBoolean(THEME_PLUGIN_ID, darkKey(prefThemeId), darkById, null);
+ }
+ String dark = prefService.get(darkKey(prefThemeId), null, new Preferences[] {
+ InstanceScope.INSTANCE.getNode(THEME_PLUGIN_ID), ConfigurationScope.INSTANCE.getNode(THEME_PLUGIN_ID),
+ UserScope.INSTANCE.getNode(THEME_PLUGIN_ID) });
+ return dark != null ? Boolean.parseBoolean(dark) : darkById;
+ }
+
+ /**
+ * The dark theme to fall back to, preferring the one shipped with the platform
+ * over a dark theme contributed by someone else.
+ */
+ private Optional findDarkTheme() {
+ List darkThemes = getThemes().stream().filter(ITheme::isDark).toList();
+ return darkThemes.stream().filter(t -> E4_DARK_THEME_ID.equals(t.getId())).findFirst()
+ .or(() -> darkThemes.stream().findFirst());
+ }
+
@Override
public ITheme getActiveTheme() {
return currentTheme;
diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/theme/ITheme.java b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/theme/ITheme.java
index 2270a0c9ce3..c6718ab15b6 100644
--- a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/theme/ITheme.java
+++ b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/theme/ITheme.java
@@ -26,4 +26,15 @@ public interface ITheme {
* @return the label
*/
String getLabel();
+
+ /**
+ * Whether this theme is meant to be used with a dark appearance, as declared by
+ * the isDarkTheme attribute of the theme extension. Implementations
+ * that do not override this keep the id based classification the platform used
+ * before the attribute existed.
+ */
+ default boolean isDark() {
+ String id = getId();
+ return id != null && id.contains("dark"); //$NON-NLS-1$
+ }
}
diff --git a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java
index 6ecf3ded110..ae135d0331f 100644
--- a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java
+++ b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java
@@ -916,13 +916,12 @@ protected static Version toMajorMinorVersion(Version version) {
protected void initializeDefaultTheme(Display display) {
IEclipsePreferences themeNode = UserScope.INSTANCE.getNode("org.eclipse.e4.ui.css.swt.theme"); //$NON-NLS-1$
String productOrAppId = getProductOrApplicationId();
- String defaultThemeId;
- if (productOrAppId != null) {
- defaultThemeId = themeNode.node(productOrAppId).get("themeid", null); //$NON-NLS-1$
- } else {
- defaultThemeId = themeNode.get("themeid", null); //$NON-NLS-1$
- }
- isDark = defaultThemeId != null && defaultThemeId.contains("dark"); //$NON-NLS-1$
+ IEclipsePreferences scopedNode = productOrAppId != null ? (IEclipsePreferences) themeNode.node(productOrAppId)
+ : themeNode;
+ String defaultThemeId = scopedNode.get("themeid", null); //$NON-NLS-1$
+ // preferences written before themes declared their appearance only carry the id
+ isDark = defaultThemeId != null
+ && scopedNode.getBoolean("themeIsDark." + defaultThemeId, defaultThemeId.contains("dark")); //$NON-NLS-1$ //$NON-NLS-2$
if (isDark) {
display.setDarkThemePreferred(true);
darkThemeShowListener = event -> {
diff --git a/bundles/org.eclipse.ui.themes/plugin.xml b/bundles/org.eclipse.ui.themes/plugin.xml
index d4dab3dd42c..0430a791a9f 100644
--- a/bundles/org.eclipse.ui.themes/plugin.xml
+++ b/bundles/org.eclipse.ui.themes/plugin.xml
@@ -5,18 +5,21 @@
point="org.eclipse.e4.ui.css.swt.theme">
diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java
index 10da19bed2c..cd61235dfd7 100644
--- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java
+++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java
@@ -44,7 +44,6 @@
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
-import org.eclipse.core.runtime.preferences.UserScope;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.css.swt.theme.ITheme;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
@@ -89,6 +88,7 @@
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
+import org.eclipse.ui.internal.themes.DefaultThemePreference;
import org.eclipse.ui.internal.themes.IThemeDescriptor;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.themes.IThemeManager;
@@ -356,10 +356,7 @@ private ITheme getSelectedTheme() {
private void openManageDefaultThemeDialog() {
String productOrAppId = getProductOrApplicationId();
- IEclipsePreferences baseNode = UserScope.INSTANCE.getNode(E4_THEME_EXTENSION_POINT);
- IEclipsePreferences scopedNode = productOrAppId != null ? (IEclipsePreferences) baseNode.node(productOrAppId)
- : baseNode;
- String currentDefaultId = scopedNode.get("themeid", null); //$NON-NLS-1$
+ String currentDefaultId = DefaultThemePreference.getThemeId();
String currentDefaultLabel = null;
if (currentDefaultId != null) {
@@ -408,21 +405,9 @@ private void openManageDefaultThemeDialog() {
int result = dialog.open();
if (result == 0 && selectedTheme != null) {
- // Set as default
- scopedNode.put("themeid", selectedTheme.getId()); //$NON-NLS-1$
- try {
- scopedNode.flush();
- } catch (BackingStoreException e) {
- WorkbenchPlugin.log("Failed to set default theme in user scope", e); //$NON-NLS-1$
- }
+ DefaultThemePreference.set(selectedTheme);
} else if (currentDefaultId != null && result == 1) {
- // Remove default
- scopedNode.remove("themeid"); //$NON-NLS-1$
- try {
- scopedNode.flush();
- } catch (BackingStoreException e) {
- WorkbenchPlugin.log("Failed to remove default theme from user scope", e); //$NON-NLS-1$
- }
+ DefaultThemePreference.remove(currentDefaultId);
}
}
@@ -513,11 +498,16 @@ public boolean performOk() {
boolean showRestartDialog = false;
String restartDialogTitle = null;
String restartDialogMessage = null;
- boolean themeChanged = false;
+ ITheme changedTheme = null;
if (isThemingPossible()) {
ITheme theme = getSelectedTheme();
- themeChanged = theme != null && !theme.equals(currentTheme);
+ boolean themeChanged = theme != null && !theme.equals(currentTheme);
+ changedTheme = themeChanged ? theme : null;
+ // Only a switch between a light and a dark theme leaves parts styled for the
+ // previous appearance behind, themes of the same appearance restyle in place.
+ boolean appearanceChanged = themeChanged
+ && (currentTheme == null || theme.isDark() != currentTheme.isDark());
boolean colorsAndFontsThemeChanged = !PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getId()
.equals(currentColorsAndFontsTheme.getId());
@@ -533,7 +523,7 @@ public boolean performOk() {
themeComboDecorator.hide();
colorFontsDecorator.hide();
- if (themeChanged || colorsAndFontsThemeChanged) {
+ if (appearanceChanged || colorsAndFontsThemeChanged) {
showRestartDialog = true;
restartDialogTitle = WorkbenchMessages.ThemeChangeWarningTitle;
restartDialogMessage = WorkbenchMessages.ThemeChangeWarningText;
@@ -551,26 +541,19 @@ public boolean performOk() {
}
if (showRestartDialog) {
- String themeId = null;
- if (themeChanged) {
- ITheme theme = getSelectedTheme();
- if (theme != null) {
- themeId = theme.getId();
- }
- }
- showRestartDialog(restartDialogTitle, restartDialogMessage, themeId);
+ showRestartDialog(restartDialogTitle, restartDialogMessage, changedTheme);
}
return super.performOk();
}
- private void showRestartDialog(String title, String warningText, String themeId) {
+ private void showRestartDialog(String title, String warningText, ITheme theme) {
boolean[] useAsDefault = { true };
MessageDialog dialog = new MessageDialog(null, title, null, warningText, MessageDialog.NONE, 2,
WorkbenchMessages.Workbench_RestartButton, WorkbenchMessages.Workbench_DontRestartButton) {
@Override
protected Control createCustomArea(Composite parent) {
- if (themeId == null) {
+ if (theme == null) {
return null;
}
Button checkbox = new Button(parent, SWT.CHECK);
@@ -582,19 +565,8 @@ protected Control createCustomArea(Composite parent) {
};
int result = dialog.open();
if (result == 0 || result == 1) { // 0: Restart, 1: Don't Restart
- if (themeId != null && useAsDefault[0]) {
- IEclipsePreferences baseNode = UserScope.INSTANCE
- .getNode(E4_THEME_EXTENSION_POINT);
- String productOrAppId = getProductOrApplicationId();
- IEclipsePreferences scopedNode = productOrAppId != null
- ? (IEclipsePreferences) baseNode.node(productOrAppId)
- : baseNode;
- scopedNode.put("themeid", themeId); //$NON-NLS-1$
- try {
- scopedNode.flush();
- } catch (BackingStoreException e) {
- WorkbenchPlugin.log("Failed to set default theme in user scope", e); //$NON-NLS-1$
- }
+ if (theme != null && useAsDefault[0]) {
+ DefaultThemePreference.set(theme);
}
}
if (result == 0) {
diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/DefaultThemePreference.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/DefaultThemePreference.java
new file mode 100644
index 00000000000..e449df9768a
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/DefaultThemePreference.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2026 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Lars Vogel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.themes;
+
+import org.eclipse.core.runtime.IProduct;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.UserScope;
+import org.eclipse.e4.ui.css.swt.theme.ITheme;
+import org.eclipse.ui.internal.WorkbenchPlugin;
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * The theme used for new workspaces and for workspaces without an explicit
+ * theme. It is recorded in the user scope, so every installation of this
+ * product sees it, together with the theme's appearance. Readers that run
+ * before the workbench, like the workspace selection dialog, rely on that
+ * appearance.
+ */
+public final class DefaultThemePreference {
+
+ private static final String THEME_PLUGIN_ID = "org.eclipse.e4.ui.css.swt.theme"; //$NON-NLS-1$
+
+ private static final String THEMEID_KEY = "themeid"; //$NON-NLS-1$
+
+ /**
+ * Prefix of the key holding a theme's appearance. The theme id is part of the
+ * key, so a record left behind by another installation cannot be mistaken for
+ * the appearance of the theme in {@link #THEMEID_KEY}.
+ */
+ private static final String THEME_IS_DARK_KEY_PREFIX = "themeIsDark."; //$NON-NLS-1$
+
+ private DefaultThemePreference() {
+ }
+
+ /** @return the id of the default theme, or {@code null} if none is set */
+ public static String getThemeId() {
+ return getNode().get(THEMEID_KEY, null);
+ }
+
+ public static void set(ITheme theme) {
+ IEclipsePreferences node = getNode();
+ node.put(THEMEID_KEY, theme.getId());
+ node.putBoolean(THEME_IS_DARK_KEY_PREFIX + theme.getId(), theme.isDark());
+ flush(node, "Failed to set default theme in user scope"); //$NON-NLS-1$
+ }
+
+ public static void remove(String themeId) {
+ IEclipsePreferences node = getNode();
+ node.remove(THEMEID_KEY);
+ node.remove(THEME_IS_DARK_KEY_PREFIX + themeId);
+ flush(node, "Failed to remove default theme from user scope"); //$NON-NLS-1$
+ }
+
+ private static IEclipsePreferences getNode() {
+ IEclipsePreferences baseNode = UserScope.INSTANCE.getNode(THEME_PLUGIN_ID);
+ String productOrAppId = getProductOrApplicationId();
+ return productOrAppId != null ? (IEclipsePreferences) baseNode.node(productOrAppId) : baseNode;
+ }
+
+ /**
+ * Returns the product ID if a product is configured, otherwise the application
+ * ID from the system property, or {@code null} if neither is available.
+ */
+ private static String getProductOrApplicationId() {
+ IProduct product = Platform.getProduct();
+ if (product != null) {
+ return product.getId();
+ }
+ return System.getProperty("eclipse.application"); //$NON-NLS-1$
+ }
+
+ private static void flush(IEclipsePreferences node, String errorMessage) {
+ try {
+ node.flush();
+ } catch (BackingStoreException e) {
+ WorkbenchPlugin.log(errorMessage, e);
+ }
+ }
+}
diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/build.properties b/tests/org.eclipse.e4.ui.tests.css.swt/build.properties
index b7ae18f3a8f..718fb2d6624 100644
--- a/tests/org.eclipse.e4.ui.tests.css.swt/build.properties
+++ b/tests/org.eclipse.e4.ui.tests.css.swt/build.properties
@@ -16,6 +16,8 @@ source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
+ plugin.xml,\
+ css/,\
test.xml,\
about.html,\
OSGI-INF/
diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/css/testTheme.css b/tests/org.eclipse.e4.ui.tests.css.swt/css/testTheme.css
new file mode 100644
index 00000000000..491a1f88b6c
--- /dev/null
+++ b/tests/org.eclipse.e4.ui.tests.css.swt/css/testTheme.css
@@ -0,0 +1 @@
+/* Empty stylesheet for the themes contributed by this test bundle. */
diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/plugin.xml b/tests/org.eclipse.e4.ui.tests.css.swt/plugin.xml
new file mode 100644
index 00000000000..14f4d68804e
--- /dev/null
+++ b/tests/org.eclipse.e4.ui.tests.css.swt/plugin.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ThemeTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ThemeTest.java
index 0a11cb6f44d..3c95dc77cc1 100644
--- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ThemeTest.java
+++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ThemeTest.java
@@ -22,6 +22,7 @@
import java.util.Dictionary;
import java.util.Hashtable;
+import java.util.List;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
@@ -50,6 +51,11 @@ public class ThemeTest {
private static final String THEMEID_KEY = "themeid";
+ private static final String PERSISTED_THEME_ID = "persisted.test";
+
+ // the appearance is recorded under a key carrying the theme id it belongs to
+ private static final String PERSISTED_THEME_DARK_KEY = "themeIsDark." + PERSISTED_THEME_ID;
+
@RegisterExtension
CssSwtEngine css = new CssSwtEngine();
@@ -133,6 +139,70 @@ void testInheritedThemeIsReadableFromPreferenceService() {
}
}
+ @Test
+ void testDarkAttributeIsReadFromThemeExtension() {
+ List themes = getThemeEngine(Display.getDefault()).getThemes();
+
+ assertTrue(findTheme(themes, "org.eclipse.e4.ui.tests.css.swt.theme.declaredDark").isDark());
+ assertFalse(findTheme(themes, "org.eclipse.e4.ui.tests.css.swt.theme.declaredLight").isDark());
+ // themes contributed before the attribute existed are classified by their id
+ assertTrue(findTheme(themes, "org.eclipse.e4.ui.tests.css.swt.theme.legacydark").isDark());
+ }
+
+ @Test
+ void testLegacyThemesKeepTheirIdBasedClassification() {
+ assertTrue(new Theme("com.example.theme.dark", "Legacy dark").isDark());
+ assertFalse(new Theme("com.example.theme.light", "Legacy light").isDark());
+ // implementations predating isDark() are classified by their id as well
+ ITheme legacy = new ITheme() {
+ @Override
+ public String getId() {
+ return "com.example.theme.dark";
+ }
+
+ @Override
+ public String getLabel() {
+ return "Legacy dark";
+ }
+ };
+ assertTrue(legacy.isDark());
+ }
+
+ @Test
+ void testDarkFlagIsPersistedWithTheThemeId() {
+ IThemeEngine themer = getThemeEngine(Display.getDefault());
+ IEclipsePreferences node = InstanceScope.INSTANCE.getNode(ThemeEngine.THEME_PLUGIN_ID);
+ ITheme previousTheme = themer.getActiveTheme();
+ String previousId = node.get(THEMEID_KEY, null);
+ String previousDark = node.get(PERSISTED_THEME_DARK_KEY, null);
+
+ try {
+ themer.setTheme(new Theme(PERSISTED_THEME_ID, "Persisted", true), true);
+
+ assertEquals(PERSISTED_THEME_ID, node.get(THEMEID_KEY, null));
+ assertTrue(node.getBoolean(PERSISTED_THEME_DARK_KEY, false));
+ } finally {
+ if (previousTheme != null) {
+ themer.setTheme(previousTheme, false);
+ }
+ putOrRemove(node, THEMEID_KEY, previousId);
+ putOrRemove(node, PERSISTED_THEME_DARK_KEY, previousDark);
+ }
+ }
+
+ private static void putOrRemove(IEclipsePreferences node, String key, String value) {
+ if (value != null) {
+ node.put(key, value);
+ } else {
+ node.remove(key);
+ }
+ }
+
+ private static ITheme findTheme(List themes, String id) {
+ return themes.stream().filter(t -> id.equals(t.getId())).findFirst()
+ .orElseThrow(() -> new AssertionError("Theme not registered: " + id));
+ }
+
private IThemeEngine getThemeEngine(Display display) {
IThemeManager manager = context.getService(themeManagerReference);
assertNotNull(manager, "Theme manager service not available");