blob: 6859bb2e4bd18521edcabfecd9ea925158e08d16 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2017 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.themes;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.e4.ui.internal.css.swt.definition.IThemeElementDefinitionOverridable;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.services.IStylingEngine;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.osgi.service.event.EventHandler;
/**
* Theme manager for the Workbench.
*
* @since 3.0
*/
public class WorkbenchThemeManager extends EventManager implements IThemeManager {
public static RGB EMPTY_COLOR_VALUE = new RGB(0, 1, 2);
private static final String SYSTEM_DEFAULT_THEME = "org.eclipse.ui.ide.systemDefault";//$NON-NLS-1$
private static WorkbenchThemeManager instance;
private IEclipseContext context;
private IEventBroker eventBroker;
public interface Events {
String TOPIC = "org/eclipse/ui/internal/themes/WorkbenchThemeManager"; //$NON-NLS-1$
String THEME_REGISTRY_RESTYLED = TOPIC + "/themeRegistryRestyled"; //$NON-NLS-1$
String THEME_REGISTRY_MODIFIED = TOPIC + "/themeRegistryModified"; //$NON-NLS-1$
}
/**
* Returns the singelton instance of the WorkbenchThemeManager
*
* @return singleton instance
*/
public static synchronized WorkbenchThemeManager getInstance() {
if (instance == null) {
instance = new WorkbenchThemeManager();
}
return instance;
}
private ITheme currentTheme;
private IPropertyChangeListener currentThemeListener = event -> {
firePropertyChange(event);
if (event.getSource() instanceof FontRegistry) {
JFaceResources.getFontRegistry().put(event.getProperty(), (FontData[]) event.getNewValue());
} else if (event.getSource() instanceof ColorRegistry) {
JFaceResources.getColorRegistry().put(event.getProperty(), (RGB) event.getNewValue());
}
};
private ColorRegistry defaultThemeColorRegistry;
private FontRegistry defaultThemeFontRegistry;
private IThemeRegistry themeRegistry;
private Map<IThemeDescriptor, ITheme> themes = new HashMap<>(7);
private EventHandler themeChangedHandler = new WorkbenchThemeChangedHandler();
private EventHandler themeRegistryModifiedHandler = new ThemeRegistryModifiedHandler();
private boolean initialized = false;
private WorkbenchThemeManager() {
}
/*
* Initialize the WorkbenchThemeManager. Determine the default theme according
* to the following rules: 1) If we're in HC mode then default to system default
* 2) Otherwise, if preference already set (e.g. via plugin_customization.ini),
* then observe that value 3) Otherwise, use our default Call dispose when we
* close.
*/
private synchronized void init() {
if (initialized) {
return;
}
initialized = true;
defaultThemeColorRegistry = new ColorRegistry(PlatformUI.getWorkbench().getDisplay());
defaultThemeFontRegistry = new FontRegistry(PlatformUI.getWorkbench().getDisplay());
// copy the font values from preferences.
FontRegistry jfaceFonts = JFaceResources.getFontRegistry();
for (Object fontRegistryKey : jfaceFonts.getKeySet()) {
String key = (String) fontRegistryKey;
defaultThemeFontRegistry.put(key, jfaceFonts.getFontData(key));
}
// Theme might be set via plugin_configuration.ini
String themeId = PrefUtil.getAPIPreferenceStore()
.getDefaultString(IWorkbenchPreferenceConstants.CURRENT_THEME_ID);
// If not set, use default
if (themeId.isEmpty())
themeId = IThemeManager.DEFAULT_THEME;
final boolean highContrast = Display.getCurrent().getHighContrast();
Display.getCurrent().addListener(SWT.Settings, event -> updateThemes());
// If in HC, *always* use the system default.
// This ignores any default theme set via plugin_customization.ini
if (highContrast)
themeId = SYSTEM_DEFAULT_THEME;
PrefUtil.getAPIPreferenceStore().setDefault(IWorkbenchPreferenceConstants.CURRENT_THEME_ID, themeId);
context = Workbench.getInstance().getService(IEclipseContext.class);
eventBroker = Workbench.getInstance().getService(IEventBroker.class);
if (eventBroker != null) {
eventBroker.subscribe(UIEvents.UILifeCycle.THEME_CHANGED, themeChangedHandler);
eventBroker.subscribe(IThemeEngine.Events.THEME_CHANGED, themeChangedHandler);
eventBroker.subscribe(Events.THEME_REGISTRY_MODIFIED, themeRegistryModifiedHandler);
}
getCurrentTheme(); // initialize the current theme
}
/*
* Update existing theme contents, descriptors, and registries. Reread the
* themes and recompute the registries.
*/
private void updateThemes() {
// This code was added to fix a windows specific issue, see Bug 19229
// However, it's causing issues on Linux, see Bug 563001
if (Util.isLinux()) {
return;
}
// reread the themes since their descriptors have changed in value
ThemeRegistryReader reader = new ThemeRegistryReader();
reader.readThemes(Platform.getExtensionRegistry(), (ThemeRegistry) getThemeRegistry());
// DEFAULT_THEME is not in getThemes() list so must be handled special
ThemeElementHelper.populateRegistry(getTheme(IThemeManager.DEFAULT_THEME), getThemeRegistry().getColors(),
PrefUtil.getInternalPreferenceStore());
IThemeDescriptor[] themeDescriptors = getThemeRegistry().getThemes();
for (IThemeDescriptor themeDescriptor : themeDescriptors) {
ITheme theme = themes.get(themeDescriptor);
// If theme is in our themes table then its already been populated
if (theme != null) {
// By the time updateThemes is called, all colors have overrides in the
// CascadingColorRegistry (due to Workbench.initializeApplicationColors), so
// we need to repopulate using getColorsFor() to update everything.
ThemeElementHelper.populateRegistry(theme, getThemeRegistry().getColorsFor(theme.getId()),
PrefUtil.getInternalPreferenceStore());
}
}
}
@Override
public void addPropertyChangeListener(IPropertyChangeListener listener) {
addListenerObject(listener);
}
/**
* Disposes all ThemeEntries.
*/
public void dispose() {
if (eventBroker != null) {
eventBroker.unsubscribe(themeChangedHandler);
eventBroker.unsubscribe(themeRegistryModifiedHandler);
}
for (Iterator<ITheme> i = themes.values().iterator(); i.hasNext();) {
ITheme theme = i.next();
theme.removePropertyChangeListener(currentThemeListener);
theme.dispose();
}
themes.clear();
}
private boolean doSetCurrentTheme(String id) {
ITheme oldTheme = currentTheme;
ITheme newTheme = getTheme(id);
if (oldTheme != newTheme && newTheme != null) {
currentTheme = newTheme;
return true;
}
return false;
}
protected void firePropertyChange(PropertyChangeEvent event) {
for (Object listener : getListeners()) {
((IPropertyChangeListener) listener).propertyChange(event);
}
}
protected void firePropertyChange(String changeId, ITheme oldTheme, ITheme newTheme) {
PropertyChangeEvent event = new PropertyChangeEvent(this, changeId, oldTheme, newTheme);
firePropertyChange(event);
}
@Override
public ITheme getCurrentTheme() {
init();
if (currentTheme == null) {
String themeId = PrefUtil.getAPIPreferenceStore().getString(IWorkbenchPreferenceConstants.CURRENT_THEME_ID);
if (themeId == null) // missing preference
setCurrentTheme(IThemeManager.DEFAULT_THEME);
else {
setCurrentTheme(themeId);
if (currentTheme == null) { // still null - the preference
// didn't resolve to a proper theme
setCurrentTheme(IThemeManager.DEFAULT_THEME);
StatusManager.getManager().handle(StatusUtil.newStatus(PlatformUI.PLUGIN_ID,
"Could not restore current theme: " + themeId, null)); //$NON-NLS-1$
}
}
}
return currentTheme;
}
/**
* Return the default color registry.
*
* @return the default color registry
*/
public ColorRegistry getDefaultThemeColorRegistry() {
init();
return defaultThemeColorRegistry;
}
/**
* Return the default font registry.
*
* @return the default font registry
*/
public FontRegistry getDefaultThemeFontRegistry() {
init();
return defaultThemeFontRegistry;
}
private ITheme getTheme(IThemeDescriptor td) {
ITheme theme = themes.get(td);
if (theme == null) {
theme = new Theme(td);
themes.put(td, theme);
}
return theme;
}
@Override
public ITheme getTheme(String id) {
init();
if (id.equals(IThemeManager.DEFAULT_THEME)) {
return getTheme((IThemeDescriptor) null);
}
IThemeDescriptor td = getThemeRegistry().findTheme(id);
if (td == null) {
return null;
}
return getTheme(td);
}
/**
* Answer the IThemeRegistry for the Workbench
*/
private IThemeRegistry getThemeRegistry() {
if (themeRegistry == null) {
themeRegistry = WorkbenchPlugin.getDefault().getThemeRegistry();
}
return themeRegistry;
}
@Override
public void removePropertyChangeListener(IPropertyChangeListener listener) {
removeListenerObject(listener);
}
@Override
public void setCurrentTheme(String id) {
init();
ITheme oldTheme = currentTheme;
if (WorkbenchThemeManager.getInstance().doSetCurrentTheme(id)) {
firePropertyChange(CHANGE_CURRENT_THEME, oldTheme, getCurrentTheme());
if (oldTheme != null) {
oldTheme.removePropertyChangeListener(currentThemeListener);
}
currentTheme.addPropertyChangeListener(currentThemeListener);
// update the preference if required.
if (!PrefUtil.getAPIPreferenceStore().getString(IWorkbenchPreferenceConstants.CURRENT_THEME_ID)
.equals(id)) {
PrefUtil.getAPIPreferenceStore().setValue(IWorkbenchPreferenceConstants.CURRENT_THEME_ID, id);
PrefUtil.saveAPIPrefs();
}
// update the jface registries
ColorRegistry jfaceColors = JFaceResources.getColorRegistry();
ColorRegistry themeColors = currentTheme.getColorRegistry();
for (Object themeColorKey : themeColors.getKeySet()) {
String key = (String) themeColorKey;
jfaceColors.put(key, themeColors.getRGB(key));
}
FontRegistry jfaceFonts = JFaceResources.getFontRegistry();
FontRegistry themeFonts = currentTheme.getFontRegistry();
for (Object themeFontKey : themeFonts.getKeySet()) {
String key = (String) themeFontKey;
jfaceFonts.put(key, themeFonts.getFontData(key));
}
if (oldTheme != null && eventBroker != null) {
eventBroker.send(UIEvents.UILifeCycle.THEME_CHANGED, null);
eventBroker.send(UIEvents.UILifeCycle.THEME_DEFINITION_CHANGED, context.get(MApplication.class));
}
}
}
public static class WorkbenchThemeChangedHandler implements EventHandler {
@Override
public void handleEvent(org.osgi.service.event.Event event) {
IStylingEngine engine = getStylingEngine();
ThemeRegistry themeRegistry = getThemeRegistry();
FontRegistry fontRegistry = getFontRegistry();
ColorRegistry colorRegistry = getColorRegistry();
resetThemeRegistries(themeRegistry, fontRegistry, colorRegistry);
overrideAlreadyExistingDefinitions(event, engine, themeRegistry, fontRegistry, colorRegistry);
addNewDefinitions(event, engine, themeRegistry, fontRegistry, colorRegistry);
sendThemeRegistryRestyledEvent();
}
protected IStylingEngine getStylingEngine() {
return getContext().get(IStylingEngine.class);
}
protected ThemeRegistry getThemeRegistry() {
return (ThemeRegistry) getContext().get(IThemeRegistry.class);
}
protected FontRegistry getFontRegistry() {
return getColorsAndFontsTheme().getFontRegistry();
}
protected ColorRegistry getColorRegistry() {
return getColorsAndFontsTheme().getColorRegistry();
}
protected void sendThemeRegistryRestyledEvent() {
IEventBroker eventBroker = getContext().get(IEventBroker.class);
eventBroker.send(Events.THEME_REGISTRY_RESTYLED, null);
}
protected ITheme getColorsAndFontsTheme() {
return WorkbenchThemeManager.getInstance().getCurrentTheme();
}
private IEclipseContext getContext() {
return WorkbenchThemeManager.getInstance().context;
}
protected org.eclipse.e4.ui.css.swt.theme.ITheme getTheme(org.osgi.service.event.Event event) {
org.eclipse.e4.ui.css.swt.theme.ITheme theme = (org.eclipse.e4.ui.css.swt.theme.ITheme) event
.getProperty(IThemeEngine.Events.THEME);
if (theme == null) {
IThemeEngine themeEngine = getContext().get(IThemeEngine.class);
theme = themeEngine != null ? themeEngine.getActiveTheme() : null;
}
return theme;
}
// At this moment we don't remove the definitions added by CSS since we
// don't want to modify the 3.x theme registries api
protected void resetThemeRegistries(ThemeRegistry themeRegistry, FontRegistry fontRegistry,
ColorRegistry colorRegistry) {
for (FontDefinition def : themeRegistry.getFonts()) {
if (def.isOverridden()) {
def.resetToDefaultValue();
fontRegistry.put(def.getId(), def.getValue() != null ? def.getValue()
: PreferenceConverter.getFontDataArrayDefaultDefault());
}
}
for (ColorDefinition def : themeRegistry.getColors()) {
if (def.isOverridden()) {
def.resetToDefaultValue();
colorRegistry.put(def.getId(), def.getValue() != null ? def.getValue() : EMPTY_COLOR_VALUE);
}
}
}
protected void overrideAlreadyExistingDefinitions(org.osgi.service.event.Event event, IStylingEngine engine,
ThemeRegistry themeRegistry, FontRegistry fontRegistry, ColorRegistry colorRegistry) {
IPreferenceStore store = PrefUtil.getInternalPreferenceStore();
org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme = getTheme(event);
ITheme theme = getColorsAndFontsTheme();
for (FontDefinition fontDefinition : themeRegistry.getFonts()) {
engine.style(fontDefinition);
if (fontDefinition.isOverridden()) {
populateDefinition(cssTheme, theme, fontRegistry, fontDefinition, store);
if (!fontDefinition.isModifiedByUser()) {
fontRegistry.put(fontDefinition.getId(), fontDefinition.getValue());
}
}
}
for (ColorDefinition colorDefinition : themeRegistry.getColors()) {
engine.style(colorDefinition);
if (colorDefinition.isOverridden()) {
populateDefinition(cssTheme, theme, colorRegistry, colorDefinition, store);
if (!colorDefinition.isModifiedByUser()) {
colorRegistry.put(colorDefinition.getId(), colorDefinition.getValue());
}
}
}
}
private void addNewDefinitions(org.osgi.service.event.Event event, IStylingEngine engine,
ThemeRegistry themeRegistry, FontRegistry fontRegistry, ColorRegistry colorRegistry) {
IPreferenceStore store = PrefUtil.getInternalPreferenceStore();
org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme = getTheme(event);
ITheme theme = getColorsAndFontsTheme();
ThemesExtension themesExtension = createThemesExtension();
engine.style(themesExtension);
for (IThemeElementDefinitionOverridable<?> definition : themesExtension.getDefinitions()) {
engine.style(definition);
if (definition.isOverridden() && definition instanceof FontDefinition) {
addFontDefinition((FontDefinition) definition, themeRegistry, fontRegistry);
populateDefinition(cssTheme, theme, fontRegistry, (FontDefinition) definition, store);
} else if (definition.isOverridden() && definition instanceof ColorDefinition) {
addColorDefinition((ColorDefinition) definition, themeRegistry, colorRegistry);
populateDefinition(cssTheme, theme, colorRegistry, (ColorDefinition) definition, store);
}
}
}
private void addFontDefinition(FontDefinition definition, ThemeRegistry themeRegistry,
FontRegistry fontRegistry) {
if (themeRegistry.findFont(definition.getId()) == null) {
themeRegistry.add(definition);
fontRegistry.put(definition.getId(), definition.getValue());
}
}
private void addColorDefinition(ColorDefinition definition, ThemeRegistry themeRegistry,
ColorRegistry colorRegistry) {
if (themeRegistry.findColor(definition.getId()) == null) {
themeRegistry.add(definition);
colorRegistry.put(definition.getId(), definition.getValue());
}
}
protected ThemesExtension createThemesExtension() {
return new ThemesExtension();
}
protected void populateDefinition(org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme, ITheme theme,
ColorRegistry registry, ColorDefinition definition, IPreferenceStore store) {
ThemeElementHelper.populateDefinition(cssTheme, theme, registry, definition, store);
}
protected void populateDefinition(org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme, ITheme theme,
FontRegistry registry, FontDefinition definition, IPreferenceStore store) {
ThemeElementHelper.populateDefinition(cssTheme, theme, registry, definition, store);
}
}
public static class ThemeRegistryModifiedHandler implements EventHandler {
@Override
public void handleEvent(org.osgi.service.event.Event event) {
populateThemeRegistries(getThemeRegistry(), getFontRegistry(), getColorRegistry(), getTheme(),
getColorsAndFontsTheme());
sendThemeDefinitionChangedEvent();
}
protected org.eclipse.e4.ui.css.swt.theme.ITheme getTheme() {
IThemeEngine themeEngine = getContext().get(IThemeEngine.class);
return themeEngine != null ? themeEngine.getActiveTheme() : null;
}
protected ThemeRegistry getThemeRegistry() {
return (ThemeRegistry) getContext().get(IThemeRegistry.class);
}
protected FontRegistry getFontRegistry() {
return getColorsAndFontsTheme().getFontRegistry();
}
protected ColorRegistry getColorRegistry() {
return getColorsAndFontsTheme().getColorRegistry();
}
protected ITheme getColorsAndFontsTheme() {
return WorkbenchThemeManager.getInstance().getCurrentTheme();
}
private IEclipseContext getContext() {
return WorkbenchThemeManager.getInstance().context;
}
protected void sendThemeDefinitionChangedEvent() {
MApplication application = getContext().get(MApplication.class);
getInstance().eventBroker.send(UIEvents.UILifeCycle.THEME_DEFINITION_CHANGED, application);
}
protected void populateThemeRegistries(ThemeRegistry themeRegistry, FontRegistry fontRegistry,
ColorRegistry colorRegistry, org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme, ITheme theme) {
IPreferenceStore store = PrefUtil.getInternalPreferenceStore();
for (FontDefinition definition : themeRegistry.getFonts()) {
if (definition.isOverridden() || definition.isAddedByCss()) {
populateDefinition(cssTheme, theme, fontRegistry, definition, store);
}
}
for (ColorDefinition definition : themeRegistry.getColors()) {
if (definition.isOverridden() || definition.isAddedByCss()) {
populateDefinition(cssTheme, theme, colorRegistry, definition, store);
}
}
}
protected void populateDefinition(org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme, ITheme theme,
ColorRegistry registry, ColorDefinition definition, IPreferenceStore store) {
ThemeElementHelper.populateDefinition(cssTheme, theme, registry, definition, store);
}
protected void populateDefinition(org.eclipse.e4.ui.css.swt.theme.ITheme cssTheme, ITheme theme,
FontRegistry registry, FontDefinition definition, IPreferenceStore store) {
ThemeElementHelper.populateDefinition(cssTheme, theme, registry, definition, store);
}
}
}