| /******************************************************************************* |
| * 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); |
| } |
| } |