diff --git a/bundles/org.eclipse.ui.ide/plugin.properties b/bundles/org.eclipse.ui.ide/plugin.properties index ad8338928c5..c48a4dc3fc2 100644 --- a/bundles/org.eclipse.ui.ide/plugin.properties +++ b/bundles/org.eclipse.ui.ide/plugin.properties @@ -343,6 +343,8 @@ multiFilterProvider.description = Match many attributes of files and folders menu.window.appearance.label=Appearance menu.window.appearance.mnemonic=a menu.window.appearance.tooltip=Change appearance of Windows and Toolbars +menu.window.appearance.themes.label=Themes +menu.window.appearance.themes.mnemonic=T menu.copyDetails.label=Cop&y Details command.edit.copy.mnemonic = C diff --git a/bundles/org.eclipse.ui.ide/plugin.xml b/bundles/org.eclipse.ui.ide/plugin.xml index 8ee52ff8427..02c99caee6f 100644 --- a/bundles/org.eclipse.ui.ide/plugin.xml +++ b/bundles/org.eclipse.ui.ide/plugin.xml @@ -1794,6 +1794,19 @@ commandId="org.eclipse.ui.window.minimizePart" style="push"> + + + + + + diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/ExtensionFactory.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/ExtensionFactory.java index 577d744bc50..250740bdf58 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/ExtensionFactory.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/ExtensionFactory.java @@ -30,6 +30,7 @@ import org.eclipse.ui.internal.dialogs.WorkbenchPreferencePage; import org.eclipse.ui.internal.keys.KeysPreferencePage; import org.eclipse.ui.internal.keys.NewKeysPreferencePage; +import org.eclipse.ui.internal.menus.ThemeContributionItem; import org.eclipse.ui.internal.progress.ProgressView; import org.eclipse.ui.internal.themes.ColorsAndFontsPreferencePage; import org.eclipse.ui.internal.wizards.preferences.PreferencesExportWizard; @@ -120,6 +121,12 @@ public class ExtensionFactory implements IExecutableExtensionFactory, IExecutabl */ public static final String SHOW_IN_CONTRIBUTION = "showInContribution"; //$NON-NLS-1$ + /** + * Factory ID for the contribution listing the installed themes. Private on + * purpose, a public constant here would be new API. + */ + private static final String THEME_CONTRIBUTION = "themeContribution"; //$NON-NLS-1$ + private IConfigurationElement config; private String id; @@ -188,6 +195,9 @@ public Object create() throws CoreException { if (SHOW_IN_CONTRIBUTION.equals(id)) { return new ShowInMenu(); } + if (THEME_CONTRIBUTION.equals(id)) { + return new ThemeContributionItem(); + } throw new CoreException(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0, "Unknown id in data argument for " + getClass(), null)); //$NON-NLS-1$ diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/menus/ThemeContributionItem.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/menus/ThemeContributionItem.java new file mode 100644 index 00000000000..118d3eaa807 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/menus/ThemeContributionItem.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.menus; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.e4.ui.css.swt.theme.ITheme; +import org.eclipse.e4.ui.css.swt.theme.IThemeEngine; +import org.eclipse.jface.action.ContributionItem; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.CompoundContributionItem; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.internal.themes.DefaultThemePreference; + +/** + * Lists the installed themes and applies the one the user picks, remembering it + * for the next start. A last entry records the active theme as the default for + * new workspaces. + */ +public class ThemeContributionItem extends CompoundContributionItem { + + public ThemeContributionItem() { + } + + public ThemeContributionItem(String id) { + super(id); + } + + @Override + protected IContributionItem[] getContributionItems() { + IThemeEngine engine = PlatformUI.getWorkbench().getService(IThemeEngine.class); + // High contrast mode pins the engine to the high contrast theme, so offer no choice. + if (engine == null || PlatformUI.getWorkbench().getDisplay().getHighContrast()) { + return new IContributionItem[0]; + } + List themes = new ArrayList<>(engine.getThemes()); + themes.sort(Comparator.comparing(ITheme::getLabel, String.CASE_INSENSITIVE_ORDER)); + + List items = new ArrayList<>(); + themes.forEach(theme -> items.add(createItem(engine, theme))); + if (engine.getActiveTheme() != null) { + items.add(new Separator()); + items.add(createSetAsDefaultItem(engine)); + } + return items.toArray(IContributionItem[]::new); + } + + /** Records the active theme as the default for new workspaces. */ + private static IContributionItem createSetAsDefaultItem(IThemeEngine engine) { + return new ContributionItem() { + @Override + public void fill(Menu menu, int index) { + MenuItem item = new MenuItem(menu, SWT.PUSH, index); + item.setText(WorkbenchMessages.ThemeDefault_setDefault); + item.addListener(SWT.Selection, event -> { + ITheme activeTheme = engine.getActiveTheme(); + if (activeTheme != null) { + DefaultThemePreference.set(activeTheme); + } + }); + } + }; + } + + private static IContributionItem createItem(IThemeEngine engine, ITheme theme) { + return new ContributionItem() { + @Override + public void fill(Menu menu, int index) { + MenuItem item = new MenuItem(menu, SWT.RADIO, index); + item.setText(theme.getLabel()); + ITheme activeTheme = engine.getActiveTheme(); + item.setSelection(activeTheme != null && activeTheme.getId().equals(theme.getId())); + item.addListener(SWT.Selection, event -> { + if (!((MenuItem) event.widget).getSelection()) { + return; + } + ITheme previousTheme = engine.getActiveTheme(); + // Clicking the checked entry fires a selection too, reapplying it would + // restyle the whole workbench for nothing. + if (previousTheme != null && previousTheme.getId().equals(theme.getId())) { + return; + } + engine.setTheme(theme, true); + // Only a light/dark switch leaves parts styled for the previous appearance. + if (previousTheme != null && previousTheme.isDark() != theme.isDark()) { + offerRestart(); + } + }); + } + }; + } + + private static void offerRestart() { + MessageDialog dialog = new MessageDialog(null, WorkbenchMessages.ThemeChangeWarningTitle, null, + WorkbenchMessages.ThemeChangeWarningText, MessageDialog.NONE, 1, + WorkbenchMessages.Workbench_RestartButton, WorkbenchMessages.Workbench_DontRestartButton); + if (dialog.open() == 0) { + Display.getDefault().asyncExec(() -> PlatformUI.getWorkbench().restart()); + } + } +}