Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bundles/org.eclipse.ui.ide/plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.eclipse.ui.ide/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,19 @@
commandId="org.eclipse.ui.window.minimizePart"
style="push">
</command>
<separator
name="org.eclipse.ui.window.appearance.themesSeparator"
visible="true">
</separator>
<menu
id="org.eclipse.ui.appearance.themes"
label="%menu.window.appearance.themes.label"
mnemonic="%menu.window.appearance.themes.mnemonic">
<dynamic
class="org.eclipse.ui.ExtensionFactory:themeContribution"
id="org.eclipse.ui.menus.dynamicThemeMenu">
</dynamic>
</menu>
</menu>
</menuContribution>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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$
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <Lars.Vogel@vogella.com> - 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<ITheme> themes = new ArrayList<>(engine.getThemes());
themes.sort(Comparator.comparing(ITheme::getLabel, String.CASE_INSENSITIVE_ORDER));

List<IContributionItem> 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());
}
}
}
Loading