| /******************************************************************************* |
| * Copyright (c) 2010,2012 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 |
| *******************************************************************************/ |
| package org.eclipse.e4.ui.css.swt.internal.theme; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| 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.RegistryFactory; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| 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.theme.ITheme; |
| import org.eclipse.e4.ui.css.swt.theme.IThemeEngine; |
| 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<Theme>(); |
| private List<CSSEngine> cssEngines = new ArrayList<CSSEngine>(); |
| |
| // kept for theme notifications only |
| private Display display; |
| |
| private ITheme currentTheme; |
| |
| private List<String> globalStyles = new ArrayList<String>(); |
| private List<IResourceLocator> globalSourceLocators = new ArrayList<IResourceLocator>(); |
| |
| private HashMap<String, List<String>> stylesheets = new HashMap<String, List<String>>(); |
| private HashMap<String, List<String>> modifiedStylesheets = new HashMap<String, List<String>>(); |
| private HashMap<String, List<IResourceLocator>> sourceLocators = new HashMap<String, List<IResourceLocator>>(); |
| |
| private static final String THEMEID_KEY = "themeid"; |
| |
| 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 |
| File modDir= new File( |
| System.getProperty("user.home") + System.getProperty("file.separator") + ".e4css" + System.getProperty("file.separator")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| File[] modifiedFiles = modDir.listFiles(); |
| |
| for (IExtension e : extPoint.getExtensions()) { |
| for (IConfigurationElement ce : getPlatformMatches(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; |
| } |
| String themeId = ce.getAttribute("id") + version; |
| registerTheme( |
| themeId, |
| ce.getAttribute("label"), basestylesheeturi, |
| version); |
| |
| //check for modified files |
| if (modifiedFiles != null) { |
| int slash = originalCSSFile.lastIndexOf("/"); |
| if (slash != -1) { |
| originalCSSFile = originalCSSFile.substring(slash + 1); |
| for (int i = 0; i < modifiedFiles.length; i++) { |
| String modifiedFileName = modifiedFiles[i].getName(); |
| if (modifiedFileName.contains(".css") && modifiedFileName.equals(originalCSSFile)) { //$NON-NLS-1$ |
| // modifiedStylesheets |
| ArrayList<String> styleSheets = new ArrayList<String>(); |
| styleSheets.add(modifiedFiles[i].toURI().toString()); |
| modifiedStylesheets.put(themeId, styleSheets); |
| } |
| } |
| } |
| } |
| } catch (IllegalArgumentException e1) { |
| //TODO Can we somehow use logging? |
| e1.printStackTrace(); |
| } |
| } |
| } |
| } |
| |
| for (IExtension e : extPoint.getExtensions()) { |
| for (IConfigurationElement ce : getPlatformMatches(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 { |
| String[] themes = new String[cces.length]; |
| for (int i = 0; i < cces.length; i++) { |
| themes[i] = cces[i].getAttribute("refid"); |
| } |
| registerStylesheet( |
| "platform:/plugin/" |
| + ce.getContributor().getName() + "/" |
| + ce.getAttribute("uri"), themes); |
| |
| for (IConfigurationElement resourceEl : ce |
| .getChildren("osgiresourcelocator")) { |
| String uri = resourceEl.getAttribute("uri"); |
| if (uri != null) { |
| registerResourceLocator(new OSGiResourceLocator( |
| uri)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| //Resolve to install dir |
| registerResourceLocator(new OSGiResourceLocator("platform:/plugin/org.eclipse.platform/css/")); |
| registerResourceLocator(new FileResourcesLocatorImpl()); |
| // FIXME: perhaps ResourcesLocatorManager shouldn't have a default? |
| // registerResourceLocator(new HttpResourcesLocatorImpl()); |
| } |
| |
| 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; |
| } |
| |
| 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("ogsi.ws"); |
| |
| uri = uri.replaceAll("\\$os\\$", osname).replaceAll("\\$ws\\$", wsname); |
| |
| if (themes.length == 0) { |
| globalStyles.add(uri); |
| } else { |
| for (String t : themes) { |
| registerStyle(t, uri); |
| } |
| } |
| } |
| |
| 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<IResourceLocator>(); |
| 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<String>(); |
| 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<String>(m); |
| m.addAll(globalStyles); |
| return m; |
| } |
| |
| List<String> s = stylesheets.get(id); |
| if (s == null) { |
| s = Collections.emptyList(); |
| } |
| |
| s = new ArrayList<String>(s); |
| s.addAll(globalStyles); |
| return s; |
| |
| } |
| |
| private List<IResourceLocator> getResourceLocators(String id) { |
| List<IResourceLocator> list = new ArrayList<IResourceLocator>( |
| globalSourceLocators); |
| List<IResourceLocator> s = sourceLocators.get(id); |
| if (s != null) { |
| list.addAll(s); |
| } |
| |
| return list; |
| } |
| |
| /** |
| * Get all elements that have os/ws attributes that best match the current |
| * platform. |
| * |
| * @param elements |
| * the elements to check |
| * @return the best matches, if any |
| */ |
| private IConfigurationElement[] getPlatformMatches( |
| IConfigurationElement[] elements) { |
| Bundle bundle = FrameworkUtil.getBundle(ThemeEngine.class); |
| String osname = bundle.getBundleContext().getProperty("osgi.os"); |
| // TODO: Need to differentiate win32 versions |
| String os_version = System.getProperty("os.version"); |
| String wsname = bundle.getBundleContext().getProperty("ogsi.ws"); |
| ArrayList<IConfigurationElement> matchingElements = new ArrayList<IConfigurationElement>(); |
| for (int i = 0; i < elements.length; i++) { |
| IConfigurationElement element = elements[i]; |
| String elementOs = element.getAttribute("os"); |
| String elementWs = element.getAttribute("ws"); |
| String elementOsVersion = element.getAttribute("os_version"); |
| if (osname != null |
| && (elementOs == null || elementOs.contains(osname))) { |
| if (os_version != null && os_version.equalsIgnoreCase(elementOsVersion)) { |
| // best match |
| matchingElements.add(element); |
| continue; |
| } |
| matchingElements.add(element); |
| } else if (wsname != null && wsname.equalsIgnoreCase(elementWs)) { |
| matchingElements.add(element); |
| } |
| } |
| return (IConfigurationElement[]) matchingElements |
| .toArray(new IConfigurationElement[matchingElements.size()]); |
| } |
| |
| public void setTheme(String themeId, boolean restore) { |
| String osVersion = System.getProperty("os.version"); |
| if (osVersion != null) { |
| boolean found = false; |
| for (Theme t : themes) { |
| String version = t.getOsVersion(); |
| if (version != null && osVersion.contains(version)) { |
| String themeVersion = themeId + version; |
| 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; |
| } |
| } |
| } |
| |
| 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) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } finally { |
| if (stream != null) { |
| try { |
| stream.close(); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| } catch (MalformedURLException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| for (CSSEngine engine : cssEngines) { |
| engine.reapply(); |
| } |
| } |
| |
| if (restore) { |
| IEclipsePreferences pref = getPreferences(); |
| pref.put(THEMEID_KEY, theme.getId()); |
| try { |
| pref.flush(); |
| } catch (BackingStoreException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| sendThemeChangeEvent(restore); |
| } |
| |
| /** |
| * 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<String, Object>(); |
| 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); |
| } |
| |
| public synchronized List<ITheme> getThemes() { |
| return Collections.unmodifiableList(new ArrayList<ITheme>(themes)); |
| } |
| |
| 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 new InstanceScope().getNode(FrameworkUtil.getBundle( |
| ThemeEngine.class).getSymbolicName()); |
| } |
| |
| 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); |
| } |
| } |
| |
| public ITheme getActiveTheme() { |
| return currentTheme; |
| } |
| |
| public CSSStyleDeclaration getStyle(Object widget) { |
| for (CSSEngine engine : cssEngines) { |
| Element e = engine.getCSSElementContext(widget).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<String>() : 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<String>() : ss; |
| } |
| |
| public void resetModifiedStylesheets(ITheme selection) { |
| List<String> ss = modifiedStylesheets.remove(selection.getId()); |
| } |
| |
| public void addCSSEngine(CSSEngine cssEngine) { |
| cssEngines.add(cssEngine); |
| resetCurrentTheme(); |
| } |
| |
| public void removeCSSEngine(CSSEngine cssEngine) { |
| cssEngines.remove(cssEngine); |
| } |
| } |