blob: c79cc6d8e757161e161acaa985604d2aab73f07e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2017 Tom Schindl and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
* Brian de Alwis - added support for multiple CSS engines
* Lars Vogel <Lars.Vogel@gmail.com> - Bug 422702
* IBM Corporation - initial API and implementation
* Lucas Bullen (Red Hat Inc.) - [Bug 527806] Ship all themes for all OS & WS
*******************************************************************************/
package org.eclipse.e4.ui.css.swt.internal.theme;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.e4.ui.css.core.engine.CSSElementContext;
import org.eclipse.e4.ui.css.core.engine.CSSEngine;
import org.eclipse.e4.ui.css.core.util.impl.resources.FileResourcesLocatorImpl;
import org.eclipse.e4.ui.css.core.util.impl.resources.OSGiResourceLocator;
import org.eclipse.e4.ui.css.core.util.resources.IResourceLocator;
import org.eclipse.e4.ui.css.swt.helpers.EclipsePreferencesHelper;
import org.eclipse.e4.ui.css.swt.theme.ITheme;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.prefs.BackingStoreException;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.Element;
import org.w3c.dom.css.CSSStyleDeclaration;
public class ThemeEngine implements IThemeEngine {
private List<Theme> themes = new ArrayList<>();
private Map<String, List<String>> themesToVarients = new HashMap<>();
private List<CSSEngine> cssEngines = new ArrayList<>();
// kept for theme notifications only
private Display display;
private ITheme currentTheme;
private List<String> globalStyles = new ArrayList<>();
private List<IResourceLocator> globalSourceLocators = new ArrayList<>();
private HashMap<String, List<String>> stylesheets = new HashMap<>();
private HashMap<String, List<String>> modifiedStylesheets = new HashMap<>();
private HashMap<String, List<IResourceLocator>> sourceLocators = new HashMap<>();
private static final String THEMEID_KEY = "themeid";
public static final String THEME_PLUGIN_ID = "org.eclipse.e4.ui.css.swt.theme";
public ThemeEngine(Display display) {
this.display = display;
IExtensionRegistry registry = RegistryFactory.getRegistry();
IExtensionPoint extPoint = registry
.getExtensionPoint("org.eclipse.e4.ui.css.swt.theme");
//load any modified style sheets
Location configLocation = org.eclipse.core.runtime.Platform.getConfigurationLocation();
String e4CSSPath = null;
try {
URL locationURL = new URL(configLocation.getDataArea(ThemeEngine.THEME_PLUGIN_ID).toString());
File locationFile = new File(locationURL.getFile());
e4CSSPath = locationFile.getPath();
} catch (IOException e1) {
}
IPath path = new Path(e4CSSPath + System.getProperty("file.separator"));
File modDir= new File(path.toFile().toURI());
if (!modDir.exists()) {
modDir.mkdirs();
}
//Check for old css files
File oldModDir= new File(
System.getProperty("user.home") + System.getProperty("file.separator") + ".e4css" + System.getProperty("file.separator")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (oldModDir.exists()) {
File done = new File(oldModDir, ".processed");
if (!done.exists()) {
// copy over files into config area
try {
done.createNewFile();
File[] oldModifiedFiles = oldModDir.listFiles();
for (File oldModifiedFile : oldModifiedFiles) {
if (oldModifiedFile.getName().contains(".css")) {
copyFile(oldModifiedFile.getPath(), path
+ System.getProperty("file.separator")
+ oldModifiedFile.getName());
}
}
} catch (IOException e1) {
}
}
}
File[] modifiedFiles = modDir.listFiles();
String currentOS = Platform.getOS();
String currentWS = Platform.getWS();
for (IExtension e : extPoint.getExtensions()) {
for (IConfigurationElement ce : e.getConfigurationElements()) {
if (ce.getName().equals("theme")) {
try {
String version = ce.getAttribute("os_version");
if (version == null) {
version ="";
}
String originalCSSFile;
String basestylesheeturi = originalCSSFile = ce
.getAttribute("basestylesheeturi");
if (!basestylesheeturi.startsWith("platform:/plugin/")) {
basestylesheeturi = "platform:/plugin/"
+ ce.getContributor().getName() + "/"
+ basestylesheeturi;
}
final String themeBaseId = ce.getAttribute("id") + version;
String themeId = themeBaseId;
String label = ce.getAttribute("label");
String os = ce.getAttribute("os");
String ws = ce.getAttribute("ws");
if ((os != null && !os.equals(currentOS)) || (ws != null && !ws.equals(currentWS))) {
if (!themesToVarients.containsKey(themeBaseId)) {
themesToVarients.put(themeBaseId, new ArrayList<>());
}
themeId = getVarientThemeId(themeBaseId, os, ws);
themesToVarients.get(themeBaseId).add(themeId);
label = getVarientThemeLabel(label, os, ws);
}
registerTheme(
themeId, label, basestylesheeturi,
version);
//check for modified files
if (modifiedFiles != null) {
int slash = originalCSSFile.lastIndexOf('/');
if (slash != -1) {
originalCSSFile = originalCSSFile.substring(slash + 1);
for (File modifiedFile : modifiedFiles) {
String modifiedFileName = modifiedFile.getName();
if (modifiedFileName.contains(".css") && modifiedFileName.equals(originalCSSFile)) { //$NON-NLS-1$
// modifiedStylesheets
ArrayList<String> styleSheets = new ArrayList<>();
styleSheets.add(modifiedFile.toURI().toString());
modifiedStylesheets.put(themeId, styleSheets);
}
}
}
}
} catch (IllegalArgumentException e1) {
ThemeEngineManager.logError(e1.getMessage(), e1);
}
}
}
}
for (IExtension e : extPoint.getExtensions()) {
for (IConfigurationElement ce : e.getConfigurationElements()) {
if (ce.getName().equals("stylesheet")) {
IConfigurationElement[] cces = ce.getChildren("themeid");
if (cces.length == 0) {
registerStylesheet("platform:/plugin/"
+ ce.getContributor().getName() + "/"
+ ce.getAttribute("uri"));
for (IConfigurationElement resourceEl : ce
.getChildren("osgiresourcelocator")) {
String uri = resourceEl.getAttribute("uri");
if (uri != null) {
registerResourceLocator(new OSGiResourceLocator(
uri));
}
}
} else {
List<String> themes = new ArrayList<>();
for (IConfigurationElement cce : cces) {
String refid = cce.getAttribute("refid");
List<String> varientOSList = themesToVarients.get(refid);
if (varientOSList != null) {
themes.addAll(varientOSList);
}
themes.add(refid);
}
registerStylesheet(
"platform:/plugin/" + ce.getContributor().getName() + "/" + ce.getAttribute("uri"),
themes.toArray(new String[themes.size()]));
for (IConfigurationElement resourceEl : ce
.getChildren("osgiresourcelocator")) {
String uri = resourceEl.getAttribute("uri");
if (uri != null) {
registerResourceLocator(new OSGiResourceLocator(
uri));
}
}
}
}
}
}
// register a default resolver for platform uri's
registerResourceLocator(new OSGiResourceLocator(
"platform:/plugin/org.eclipse.ui.themes/css/"));
// register a default resolver for file uri's
registerResourceLocator(new FileResourcesLocatorImpl());
// FIXME: perhaps ResourcesLocatorManager shouldn't have a default?
// registerResourceLocator(new HttpResourcesLocatorImpl());
}
private String getVarientThemeId(String id, String os, String ws) {
if (os != null) {
id += '_' + os;
}
if (ws != null) {
id += '-' + ws;
}
return id;
}
private String getVarientThemeLabel(String label, String os, String ws) {
String currentOS = Platform.getOS();
String currentWS = Platform.getWS();
if (os != null && !os.equals(currentOS)) {
String osName;
switch (os) {
case Platform.OS_AIX: osName="AIX";break;
case Platform.OS_HPUX: osName="HP/UX";break;
case Platform.OS_LINUX: osName="Linux";break;
case Platform.OS_MACOSX: osName="Mac OS X";break;
case Platform.OS_QNX: osName="QNX";break;
case Platform.OS_SOLARIS: osName="Solaris";break;
case Platform.OS_WIN32: osName="Windows";break;
default: osName=os;break;
}
label += " [" + osName;
}
if (ws != null && !ws.equals(currentWS)) {
String wsName;
switch (ws) {
case Platform.WS_CARBON: wsName="Carbon";break;
case Platform.WS_COCOA: wsName="Cocoa";break;
case Platform.WS_GTK: wsName="GTK";break;
case Platform.WS_MOTIF: wsName="Motif";break;
case Platform.WS_PHOTON: wsName="Photon";break;
case Platform.WS_WPF: wsName="WPF";break;
default: wsName=ws;break;
}
label += " - " + wsName;
}
if (os != null && !os.equals(currentOS)) {
label += "]";
}
return label;
}
@Override
public synchronized ITheme registerTheme(String id, String label,
String basestylesheetURI) throws IllegalArgumentException {
return registerTheme(id, label, basestylesheetURI, "");
}
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");
}
}
Theme theme = new Theme(id, label);
if (osVersion != "") {
theme.setOsVersion(osVersion);
}
themes.add(theme);
registerStyle(id, basestylesheetURI);
return theme;
}
@Override
public synchronized void registerStylesheet(String uri, String... themes) {
Bundle bundle = FrameworkUtil.getBundle(ThemeEngine.class);
String osname = bundle.getBundleContext().getProperty("osgi.os");
String wsname = bundle.getBundleContext().getProperty("osgi.ws");
uri = uri.replaceAll("\\$os\\$", osname).replaceAll("\\$ws\\$", wsname);
if (themes.length == 0) {
globalStyles.add(uri);
} else {
for (String t : themes) {
registerStyle(t, uri);
}
}
}
@Override
public synchronized void registerResourceLocator(IResourceLocator locator,
String... themes) {
if (themes.length == 0) {
globalSourceLocators.add(locator);
} else {
for (String t : themes) {
List<IResourceLocator> list = sourceLocators.get(t);
if (list == null) {
list = new ArrayList<>();
sourceLocators.put(t, list);
}
list.add(locator);
}
}
}
private void registerStyle(String id, String stylesheet) {
List<String> s = stylesheets.get(id);
if (s == null) {
s = new ArrayList<>();
stylesheets.put(id, s);
}
s.add(stylesheet);
}
private List<String> getAllStyles(String id) {
// check for any modifications first
List<String> m = modifiedStylesheets.get(id);
if (m != null) {
m = new ArrayList<>(m);
m.addAll(globalStyles);
return m;
}
List<String> s = stylesheets.get(id);
if (s == null) {
s = Collections.emptyList();
}
s = new ArrayList<>(s);
s.addAll(globalStyles);
return s;
}
private List<IResourceLocator> getResourceLocators(String id) {
List<IResourceLocator> list = new ArrayList<>(
globalSourceLocators);
List<IResourceLocator> s = sourceLocators.get(id);
if (s != null) {
list.addAll(s);
}
return list;
}
@Override
public void setTheme(String themeId, boolean restore) {
String osVersion = System.getProperty("os.version");
if (osVersion != null) {
boolean found = false;
for (Theme t : themes) {
String osVersionList = t.getOsVersion();
if (osVersionList != null) {
String[] osVersions = osVersionList.split(","); //$NON-NLS-1$
for (String osVersionFromTheme : osVersions) {
if (osVersionFromTheme != null && osVersion.contains(osVersionFromTheme)) {
String themeVersion = themeId + osVersionList;
if (t.getId().equals(themeVersion)) {
setTheme(t, restore);
found = true;
break;
}
}
}
}
}
if (found) {
return;
}
}
//try generic
for (Theme t : themes) {
if (t.getId().equals(themeId)) {
setTheme(t, restore);
break;
}
}
}
@Override
public void setTheme(ITheme theme, boolean restore) {
setTheme(theme, restore, false);
}
public void setTheme(ITheme theme, boolean restore, boolean force) {
Assert.isNotNull(theme, "The theme must not be null");
if (this.currentTheme != theme || force) {
if (currentTheme != null) {
for (IResourceLocator l : getResourceLocators(currentTheme
.getId())) {
for (CSSEngine engine : cssEngines) {
engine.getResourcesLocatorManager()
.unregisterResourceLocator(l);
}
}
}
this.currentTheme = theme;
for (CSSEngine engine : cssEngines) {
engine.reset();
}
for (IResourceLocator l : getResourceLocators(theme.getId())) {
for (CSSEngine engine : cssEngines) {
engine.getResourcesLocatorManager()
.registerResourceLocator(l);
}
}
for (String stylesheet : getAllStyles(theme.getId())) {
URL url;
InputStream stream = null;
try {
url = FileLocator.resolve(new URL(stylesheet.toString()));
for (CSSEngine engine : cssEngines) {
try {
stream = url.openStream();
InputSource source = new InputSource();
source.setByteStream(stream);
source.setURI(url.toString());
engine.parseStyleSheet(source);
} catch (IOException e) {
ThemeEngineManager.logError(e.getMessage(), e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
ThemeEngineManager.logError(e.getMessage(), e);
}
}
}
}
} catch (IOException e) {
ThemeEngineManager.logError(e.getMessage(), e);
}
}
}
if (restore) {
IEclipsePreferences pref = getPreferences();
EclipsePreferencesHelper.setPreviousThemeId(pref.get(THEMEID_KEY, null));
EclipsePreferencesHelper.setCurrentThemeId(theme.getId());
pref.put(THEMEID_KEY, theme.getId());
try {
pref.flush();
} catch (BackingStoreException e) {
ThemeEngineManager.logError(e.getMessage(), e);
}
}
sendThemeChangeEvent(restore);
for (CSSEngine engine : cssEngines) {
engine.reapply();
}
}
/**
* Broadcast theme-change event using OSGi Event Admin.
*/
private void sendThemeChangeEvent(boolean restore) {
EventAdmin eventAdmin = getEventAdmin();
if (eventAdmin == null) {
return;
}
Map<String, Object> data = new HashMap<>();
data.put(IThemeEngine.Events.THEME_ENGINE, this);
data.put(IThemeEngine.Events.THEME, currentTheme);
data.put(IThemeEngine.Events.DEVICE, display);
data.put(IThemeEngine.Events.RESTORE, restore);
Event event = new Event(IThemeEngine.Events.THEME_CHANGED, data);
eventAdmin.sendEvent(event); // synchronous
}
public List<IResourceLocator> getCurrentResourceLocators() {
if(currentTheme == null) { return Collections.emptyList(); }
return getResourceLocators(currentTheme.getId());
}
private EventAdmin getEventAdmin() {
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
if (bundle == null) {
return null;
}
BundleContext context = bundle.getBundleContext();
ServiceReference<EventAdmin> eventAdminRef = context
.getServiceReference(EventAdmin.class);
return context.getService(eventAdminRef);
}
@Override
public synchronized List<ITheme> getThemes() {
return Collections.unmodifiableList(new ArrayList<ITheme>(themes));
}
@Override
public void applyStyles(Object widget, boolean applyStylesToChildNodes) {
for (CSSEngine engine : cssEngines) {
Object element = engine.getElement(widget);
if (element != null) {
engine.applyStyles(element, applyStylesToChildNodes);
}
}
}
private String getPreferenceThemeId() {
return getPreferences().get(THEMEID_KEY, null);
}
private IEclipsePreferences getPreferences() {
return InstanceScope.INSTANCE.getNode(
FrameworkUtil.getBundle(
ThemeEngine.class).getSymbolicName());
}
void copyFile(String from, String to) throws IOException {
FileInputStream fStream = null;
BufferedOutputStream outputStream = null;
try {
fStream = new FileInputStream(from);
outputStream = new BufferedOutputStream(new FileOutputStream(to));
byte[] buffer = new byte[4096];
int c;
while ((c = fStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, c);
}
} finally {
if (fStream != null) {
fStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
@Override
public void restore(String alternateTheme) {
String prefThemeId = getPreferenceThemeId();
boolean flag = true;
if (prefThemeId != null) {
for (ITheme t : getThemes()) {
if (prefThemeId.equals(t.getId())) {
setTheme(t, false);
flag = false;
break;
}
}
}
if (alternateTheme != null && flag) {
setTheme(alternateTheme, false);
}
}
@Override
public ITheme getActiveTheme() {
return currentTheme;
}
@Override
public CSSStyleDeclaration getStyle(Object widget) {
for (CSSEngine engine : cssEngines) {
CSSElementContext context = engine.getCSSElementContext(widget);
if (context != null) {
Element e = context.getElement();
if (e != null) {
return engine.getViewCSS().getComputedStyle(e, null);
}
}
}
return null;
}
public List<String> getStylesheets(ITheme selection) {
List<String> ss = stylesheets.get(selection.getId());
return ss == null ? new ArrayList<>() : ss;
}
public void themeModified(ITheme theme, List<String> paths) {
modifiedStylesheets.put(theme.getId(), paths);
setTheme(theme, false, true);
}
public void resetCurrentTheme() {
if (currentTheme != null) {
setTheme(currentTheme, false, true);
}
}
public List<String> getModifiedStylesheets(ITheme selection) {
List<String> ss = modifiedStylesheets.get(selection.getId());
return ss == null ? new ArrayList<>() : ss;
}
public void resetModifiedStylesheets(ITheme selection) {
modifiedStylesheets.remove(selection.getId());
}
@Override
public void addCSSEngine(CSSEngine cssEngine) {
cssEngines.add(cssEngine);
resetCurrentTheme();
}
public Collection<CSSEngine> getCSSEngines() {
return cssEngines;
}
@Override
public void removeCSSEngine(CSSEngine cssEngine) {
cssEngines.remove(cssEngine);
}
}