From 1233dcea2383bfe5c83e5ec33d0c502afff22601 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 4 Oct 2020 12:43:34 +0200 Subject: [PATCH] Improve CMS theming. --- .../argeo/cms/e4/rap/CmsLoginLifecycle.java | 4 +- .../src/org/argeo/cms/ui/AbstractCmsApp.java | 70 +++++ .../src/org/argeo/cms/ui/CmsApp.java | 6 + .../src/org/argeo/cms/ui/CmsAppListener.java | 7 + .../src/org/argeo/cms/ui/CmsTheme.java | 69 +++++ .../src/org/argeo/cms/ui/CmsView.java | 28 +- .../org/argeo/cms/ui/script/CmsScriptApp.java | 14 +- .../src/org/argeo/cms/ui/script/Theme.java | 18 -- .../argeo/cms/ui/util/AbstractCmsTheme.java | 91 +++++++ .../org/argeo/cms/ui/util/BundleCmsTheme.java | 244 ++++++++++++++++++ .../src/org/argeo/cms/ui/util/CmsIcon.java | 25 ++ .../src/org/argeo/cms/ui/util/CmsStyle.java | 5 +- .../src/org/argeo/cms/ui/util/CmsTheme.java | 202 --------------- .../src/org/argeo/cms/ui/util/CmsUiUtils.java | 7 +- .../argeo/cms/ui/util/LoginEntryPoint.java | 4 +- .../src/org/argeo/cms/ui/util/SimpleApp.java | 50 +++- .../src/org/argeo/cms/ui/util/ThemeUtils.java | 50 ---- .../argeo/cms/web/AbstractCmsEntryPoint.java | 3 +- .../util => web}/BundleResourceLoader.java | 2 +- .../argeo/cms/web/CmsThemeResourceLoader.java | 23 ++ .../src/org/argeo/cms/web/CmsWebApp.java | 100 ++++++- .../src/org/argeo/cms/web/MinimalWebApp.java | 17 +- .../src/org/argeo/cms/web/WebThemeUtils.java | 29 +++ .../src/org/argeo/util/LangUtils.java | 12 +- 24 files changed, 774 insertions(+), 306 deletions(-) create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java delete mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/script/Theme.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java delete mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsTheme.java delete mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/util/ThemeUtils.java rename org.argeo.cms.ui/src/org/argeo/cms/{ui/util => web}/BundleResourceLoader.java (96%) create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/web/CmsThemeResourceLoader.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/web/WebThemeUtils.java diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java index f7a1ed667..acdbd07a3 100644 --- a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java +++ b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java @@ -18,7 +18,6 @@ import org.argeo.cms.ui.dialogs.CmsFeedback; import org.argeo.cms.ui.util.SimpleImageManager; import org.argeo.cms.ui.util.SimpleUxContext; import org.argeo.cms.ui.widgets.auth.CmsLoginShell; -import org.argeo.eclipse.ui.specific.UiContext; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; @@ -60,8 +59,9 @@ public class CmsLoginLifecycle implements CmsView { Subject subject = Subject.getSubject(AccessController.getContext()); Display display = Display.getCurrent(); - UiContext.setData(CmsView.KEY, this); +// UiContext.setData(CmsView.KEY, this); CmsLoginShell loginShell = new CmsLoginShell(this); + CmsView.registerCmsView(loginShell.getShell(), this); loginShell.setSubject(subject); try { // try pre-auth diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java new file mode 100644 index 000000000..9a1e41fd1 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java @@ -0,0 +1,70 @@ +package org.argeo.cms.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.eclipse.rap.rwt.RWT; + +public abstract class AbstractCmsApp implements CmsApp { + private Map themes = Collections.synchronizedSortedMap(new TreeMap<>()); + + private List cmsAppListeners = new ArrayList<>(); + + @Override + public Set getUiNames() { + // TODO Auto-generated method stub + return null; + } + + protected abstract String getThemeId(String uiName); + + @Override + public CmsTheme getTheme(String uiName) { + String themeId = getThemeId(uiName); + if (themeId == null) + return null; + return themes.get(themeId); + } + + protected boolean allThemesAvailable() { + boolean themeMissing = false; + uiNames: for (String uiName : getUiNames()) { + String themeId = getThemeId(uiName); + if (RWT.DEFAULT_THEME_ID.equals(themeId)) + continue uiNames; + if (!themes.containsKey(themeId)) { + themeMissing = true; + break uiNames; + } + } + return !themeMissing; + } + + public void addTheme(CmsTheme theme, Map properties) { + themes.put(theme.getThemeId(), theme); + if (allThemesAvailable()) + for (CmsAppListener listener : cmsAppListeners) + listener.themingUpdated(); + } + + public void removeTheme(CmsTheme theme, Map properties) { + themes.remove(theme.getThemeId()); + } + + @Override + public void addCmsAppListener(CmsAppListener listener) { + cmsAppListeners.add(listener); + if (allThemesAvailable()) + listener.themingUpdated(); + } + + @Override + public void removeCmsAppListener(CmsAppListener listener) { + cmsAppListeners.remove(listener); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java index b8d382562..dc5843142 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java @@ -9,4 +9,10 @@ public interface CmsApp { Set getUiNames(); void initUi(String uiName, Composite parent); + + CmsTheme getTheme(String uiName); + + void addCmsAppListener(CmsAppListener listener); + + void removeCmsAppListener(CmsAppListener listener); } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java new file mode 100644 index 000000000..1fe3678f2 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java @@ -0,0 +1,7 @@ +package org.argeo.cms.ui; + +/** Notifies important events in a {@link CmsApp} life cycle. */ +public interface CmsAppListener { + /** Theming has been updated and should be reloaded. */ + void themingUpdated(); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java new file mode 100644 index 000000000..37f52a35e --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java @@ -0,0 +1,69 @@ +package org.argeo.cms.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; + +/** A CMS theme which can be applied to web apps as well as desktop apps. */ +public interface CmsTheme { + /** Unique ID of this theme. */ + String getThemeId(); + + /** + * Load a resource as an input stream, base don its relative path, or + * null if not found + */ + InputStream getResourceAsStream(String resourceName) throws IOException; + + /** Relative paths to RAP specific CSS. */ + Set getRapCssPaths(); + + /** Relative paths to images such as icons. */ + Set getImagesPaths(); + + /** Tags to be added to the header section of the HTML page. */ + String getHtmlHeaders(); + + /** The image registered at this path, or null if not found. */ + Image getImage(String path); + + /** The default icon size (typically the smallest). */ + Integer getDefaultIconSize(); + + /** + * And icon with this file name (without the extension), with a best effort to + * find the appropriate size, or null if not found. + * + * @param name An icon file name without path and extension. + * @param preferredSize the preferred size, if null, + * {@link #getDefaultIconSize()} will be tried. + */ + Image getIcon(String name, Integer preferredSize); + + static CmsTheme getCmsTheme(Composite parent) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + return (CmsTheme) topShell.getData(CmsTheme.class.getName()); + } + + static void registerCmsTheme(Shell shell, CmsTheme theme) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsTheme.class.getName()) != null) { + CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); + throw new IllegalArgumentException( + "Theme " + registeredTheme.getThemeId() + " already registered in this shell"); + } + topShell.setData(CmsTheme.class.getName(), theme); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java index 6d70935d7..ce0acb89f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java @@ -2,9 +2,12 @@ package org.argeo.cms.ui; import javax.security.auth.login.LoginContext; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; + /** Provides interaction with the CMS system. */ public interface CmsView { - String KEY = "org.argeo.cms.ui.view"; + //String KEY = "org.argeo.cms.ui.view"; UxContext getUxContext(); @@ -24,4 +27,27 @@ public interface CmsView { CmsImageManager getImageManager(); boolean isAnonymous(); + + static CmsView getCmsView(Composite parent) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + return (CmsView) topShell.getData(CmsView.class.getName()); + } + + static void registerCmsView(Shell shell, CmsView view) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsView.class.getName()) != null) { + CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName()); + throw new IllegalArgumentException( + "Cms view " + registeredView + " already registered in this shell"); + } + shell.setData(CmsView.class.getName(), view); + } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/script/CmsScriptApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/script/CmsScriptApp.java index d3267004e..3f0987199 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/script/CmsScriptApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/script/CmsScriptApp.java @@ -24,10 +24,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; import org.argeo.cms.ui.CmsConstants; +import org.argeo.cms.ui.CmsTheme; import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.BundleResourceLoader; import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.web.BundleResourceLoader; import org.argeo.cms.web.SimpleErgonomics; +import org.argeo.cms.web.WebThemeUtils; import org.eclipse.rap.rwt.application.Application; import org.eclipse.rap.rwt.application.Application.OperationMode; import org.eclipse.rap.rwt.application.ApplicationConfiguration; @@ -54,7 +56,7 @@ public class CmsScriptApp implements Branding { private String repo = "(cn=node)"; // private Branding branding = new Branding(); - private Theme theme; + private CmsTheme theme; private List resources = new ArrayList<>(); @@ -99,8 +101,8 @@ public class CmsScriptApp implements Branding { } if (theme != null) { - theme.apply(application); - String themeHeaders = theme.getAdditionalHeaders(); + WebThemeUtils.apply(application, theme); + String themeHeaders = theme.getHtmlHeaders(); if (themeHeaders != null) { if (additionalHeaders == null) additionalHeaders = themeHeaders; @@ -269,11 +271,11 @@ public class CmsScriptApp implements Branding { // TODO } - public Theme getTheme() { + public CmsTheme getTheme() { return theme; } - public void setTheme(Theme theme) { + public void setTheme(CmsTheme theme) { this.theme = theme; } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/script/Theme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/script/Theme.java deleted file mode 100644 index c053b4700..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/script/Theme.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.ui.script; - -import org.argeo.cms.ui.util.CmsTheme; -import org.osgi.framework.BundleContext; - -/** @deprecated Use CmsTheme instead. */ -@Deprecated -public class Theme extends CmsTheme { - - public Theme(BundleContext bundleContext, String symbolicName) { - super(bundleContext, symbolicName); - } - - public Theme(BundleContext bundleContext) { - super(bundleContext); - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java new file mode 100644 index 000000000..5ec931e71 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java @@ -0,0 +1,91 @@ +package org.argeo.cms.ui.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.argeo.cms.ui.CmsTheme; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; + +/** Centralises some generic {@link CmsTheme} patterns. */ +public abstract class AbstractCmsTheme implements CmsTheme { + private Map imageCache = new HashMap<>(); + + private Map> iconPaths = new HashMap<>(); + + private Integer defaultIconSize = 16; + + public Image getImage(String path) { + if (!imageCache.containsKey(path)) { + try (InputStream in = getResourceAsStream(path)) { + if (in == null) + return null; + ImageData imageData = new ImageData(in); + Image image = new Image(Display.getDefault(), imageData); + imageCache.put(path, image); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return imageCache.get(path); + } + + @Override + public Image getIcon(String name, Integer preferredSize) { + if (preferredSize == null) + preferredSize = defaultIconSize; + Map subCache; + if (!iconPaths.containsKey(name)) + subCache = new HashMap<>(); + else + subCache = iconPaths.get(name); + Image image = null; + if (!subCache.containsKey(preferredSize)) { + Image bestMatchSoFar = null; + paths: for (String p : getImagesPaths()) { + int lastSlash = p.lastIndexOf('/'); + String fileName = p; + if (lastSlash >= 0) + fileName = p.substring(lastSlash + 1); + int lastDot = fileName.lastIndexOf('.'); + if (lastDot >= 0) + fileName = fileName.substring(0, lastDot); + if (fileName.equals(name)) {// matched + Image img = getImage(p); + int width = img.getBounds().width; + if (width == preferredSize) {// perfect match + subCache.put(preferredSize, p); + image = img; + break paths; + } + if (bestMatchSoFar == null) { + bestMatchSoFar = img; + } else { + if (Math.abs(width - preferredSize) < Math + .abs(bestMatchSoFar.getBounds().width - preferredSize)) + bestMatchSoFar = img; + } + } + } + + if (image == null) + image = bestMatchSoFar; + } else { + image = getImage(subCache.get(preferredSize)); + } + + if (image != null && !iconPaths.containsKey(name)) + iconPaths.put(name, subCache); + + return image; + } + + @Override + public Integer getDefaultIconSize() { + return defaultIconSize; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java new file mode 100644 index 000000000..1076b6ca2 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java @@ -0,0 +1,244 @@ +package org.argeo.cms.ui.util; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.argeo.cms.ui.CmsTheme; +import org.eclipse.swt.graphics.Image; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * Simplifies the theming of an app (only RAP is supported at this stage).
+ * + * Additional fonts listed in /fonts.txt.
+ * Additional (standard CSS) header in /header.css.
+ * RAP specific CSS files in /rap/*.css.
+ * All images added as additional resources based on extensions + * / ** /*.{png,gif,jpeg,...}.
+ */ +public class BundleCmsTheme extends AbstractCmsTheme { + public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2"; + + public final static String CMS_THEME_PROPERTY = "argeo.cms.theme"; + public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle"; + +// private final static Log log = LogFactory.getLog(BundleCmsTheme.class); + + private String themeId; + private Set rapCssPaths = new TreeSet<>(); + private Set imagesPaths = new TreeSet<>(); + + private String headerCss; + private List fonts = new ArrayList<>(); + + private String basePath; + private String rapCssPath; + private Bundle themeBundle; + + public BundleCmsTheme() { + + } + + public void init(BundleContext bundleContext, Map properties) { + initResources(bundleContext, null); + } + + public void destroy(BundleContext bundleContext, Map properties) { + + } + + @Deprecated + public BundleCmsTheme(BundleContext bundleContext) { + this(bundleContext, null); + } + + @Deprecated + public BundleCmsTheme(BundleContext bundleContext, String symbolicName) { + initResources(bundleContext, symbolicName); + } + + private void initResources(BundleContext bundleContext, String symbolicName) { + if (symbolicName == null) { + themeBundle = bundleContext.getBundle(); +// basePath = "/theme/"; +// cssPath = basePath; + } else { + themeBundle = findThemeBundle(bundleContext, symbolicName); + } + basePath = "/"; + rapCssPath = "/rap/"; +// this.themeId = RWT.DEFAULT_THEME_ID; + this.themeId = themeBundle.getSymbolicName(); + addRapStyleSheets(themeBundle); + addResources("*.png"); + addResources("*.gif"); + addResources("*.jpg"); + addResources("*.jpeg"); + addResources("*.svg"); + addResources("*.ico"); + + // fonts + URL fontsUrl = themeBundle.getEntry(basePath + "fonts.txt"); + if (fontsUrl != null) { + loadFontsUrl(fontsUrl); + } + + // common CSS header (plain CSS) + URL headerCssUrl = themeBundle.getEntry(basePath + "header.css"); + if (headerCssUrl != null) { + try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) { + headerCss = buffer.lines().collect(Collectors.joining("\n")); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot read " + headerCssUrl, e); + } + } + } + + public String getHtmlHeaders() { + StringBuilder sb = new StringBuilder(); + if (headerCss != null) { + sb.append("\n"); + } + for (String link : fonts) { + sb.append("\n"); + } + if (sb.length() == 0) + return null; + else + return sb.toString(); + } + + void addRapStyleSheets(Bundle themeBundle) { + Enumeration themeResources = themeBundle.findEntries(rapCssPath, "*.css", true); + if (themeResources == null) + return; + while (themeResources.hasMoreElements()) { + String resource = themeResources.nextElement().getPath(); + // remove first '/' so that RWT registers it + resource = resource.substring(1); + if (!resource.endsWith("/")) { +// if (rapCss.containsKey(resource)) +// log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); +// rapCss.put(resource, ssRL); + rapCssPaths.add(resource); + } + + } + + } + + void loadFontsUrl(URL url) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { + String line = null; + while ((line = in.readLine()) != null) { + line = line.trim(); + if (!line.equals("") && !line.startsWith("#")) { + fonts.add(line); + } + } + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load URL " + url, e); + } + } + + void addResources(String pattern) { + Enumeration themeResources = themeBundle.findEntries(basePath, pattern, true); + if (themeResources == null) + return; + while (themeResources.hasMoreElements()) { + String resource = themeResources.nextElement().getPath(); + // remove first '/' so that RWT registers it + resource = resource.substring(1); + if (!resource.endsWith("/")) { +// if (resources.containsKey(resource)) +// log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); +// resources.put(resource, themeBRL); + imagesPaths.add(resource); + } + + } + + } + + @Override + public InputStream getResourceAsStream(String resourceName) throws IOException { + URL res = themeBundle.getEntry(resourceName); + if (res == null) { + res = themeBundle.getResource(resourceName); + if (res == null) + return null; +// throw new IllegalArgumentException( +// "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName()); + } + return res.openStream(); + } + + public String getThemeId() { + return themeId; + } + + +// public void setThemeId(String themeId) { +// this.themeId = themeId; +// } +// +// public String getBasePath() { +// return basePath; +// } +// +// public void setBasePath(String basePath) { +// this.basePath = basePath; +// } +// +// public String getRapCssPath() { +// return rapCssPath; +// } +// +// public void setRapCssPath(String cssPath) { +// this.rapCssPath = cssPath; +// } + + @Override + public Set getRapCssPaths() { + return rapCssPaths; + } + + @Override + public Set getImagesPaths() { + return imagesPaths; + } + + private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) { + if (themeId == null) + return null; + // TODO optimize + // TODO deal with multiple versions + Bundle themeBundle = null; + if (themeId != null) { + for (Bundle bundle : bundleContext.getBundles()) + if (themeId.equals(bundle.getSymbolicName())) { + themeBundle = bundle; + break; + } + } + return themeBundle; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java new file mode 100644 index 000000000..33f13ad8a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java @@ -0,0 +1,25 @@ +package org.argeo.cms.ui.util; + +import org.argeo.cms.ui.CmsTheme; +import org.eclipse.swt.graphics.Image; + +/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */ +public interface CmsIcon { + String name(); + + default Image getSmallIcon(CmsTheme theme) { + return theme.getIcon(name(), getSmallIconSize()); + } + + default Image getBigIcon(CmsTheme theme) { + return theme.getIcon(name(), getBigIconSize()); + } + + default Integer getSmallIconSize() { + return 16; + } + + default Integer getBigIconSize() { + return 32; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java index 79d5bb682..daf78d477 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java @@ -2,11 +2,14 @@ package org.argeo.cms.ui.util; /** Can be applied to {@link Enum}s in order to generated (CSS) class names. */ public interface CmsStyle { + String name(); + default String toStyleClass() { - return getClassPrefix() + "-" + ((Enum) this).name(); + return getClassPrefix() + "-" + name(); } default String getClassPrefix() { return "cms"; } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsTheme.java deleted file mode 100644 index 1e1b586b5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsTheme.java +++ /dev/null @@ -1,202 +0,0 @@ -package org.argeo.cms.ui.util; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -/** - * Simplifies the theming of an app (only RAP is supported at this stage).
- * - * Additional fonts listed in /fonts.txt.
- * Additional (standard CSS) header in /header.css.
- * RAP specific CSS files in /rap/*.css.
- * All images added as additional resources based on extensions - * / ** /*.{png,gif,jpeg,...}.
- */ -public class CmsTheme { - public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2"; - - public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle"; - - private final static Log log = LogFactory.getLog(CmsTheme.class); - - private String themeId; - private Map css = new HashMap<>(); - private Map resources = new HashMap<>(); - - private String headerCss; - private List fonts = new ArrayList<>(); - - private String basePath; - private String cssPath; - private final Bundle themeBundle; - - public CmsTheme(BundleContext bundleContext) { - this(bundleContext, null); - } - - public CmsTheme(BundleContext bundleContext, String symbolicName) { - if (symbolicName == null) { - themeBundle = bundleContext.getBundle(); -// basePath = "/theme/"; -// cssPath = basePath; - } else { - themeBundle = ThemeUtils.findThemeBundle(bundleContext, symbolicName); - } - basePath = "/"; - cssPath = "/rap/"; -// this.themeId = RWT.DEFAULT_THEME_ID; - this.themeId = themeBundle.getSymbolicName(); - addStyleSheets(themeBundle, new BundleResourceLoader(themeBundle)); - BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle); - addResources(themeBRL, "*.png"); - addResources(themeBRL, "*.gif"); - addResources(themeBRL, "*.jpg"); - addResources(themeBRL, "*.jpeg"); - addResources(themeBRL, "*.svg"); - addResources(themeBRL, "*.ico"); - - // fonts - URL fontsUrl = themeBundle.getEntry(basePath + "fonts.txt"); - if (fontsUrl != null) { - loadFontsUrl(fontsUrl); - } - - // common CSS header (plain CSS) - URL headerCssUrl = themeBundle.getEntry(basePath + "header.css"); - if (headerCssUrl != null) { - try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) { - headerCss = buffer.lines().collect(Collectors.joining("\n")); - } catch (IOException e) { - throw new CmsException("Cannot read " + headerCssUrl, e); - } - } - - } - - public void apply(Application application) { - resources: for (String name : resources.keySet()) { - if (name.startsWith("target/")) - continue resources; // skip maven output - application.addResource(name, resources.get(name)); - if (log.isTraceEnabled()) - log.trace("Theme " + themeBundle + ": added resource " + name); - } - for (String name : css.keySet()) { - application.addStyleSheet(themeId, name, css.get(name)); - if (log.isDebugEnabled()) - log.debug("Theme " + themeBundle + ": added RAP CSS " + name); - } - } - - public String getAdditionalHeaders() { - StringBuilder sb = new StringBuilder(); - if (headerCss != null) { - sb.append("\n"); - } - for (String link : fonts) { - sb.append("\n"); - } - if (sb.length() == 0) - return null; - else - return sb.toString(); - } - - void addStyleSheets(Bundle themeBundle, ResourceLoader ssRL) { - Enumeration themeResources = themeBundle.findEntries(cssPath, "*.css", true); - if (themeResources == null) - return; - while (themeResources.hasMoreElements()) { - String resource = themeResources.nextElement().getPath(); - // remove first '/' so that RWT registers it - resource = resource.substring(1); - if (!resource.endsWith("/")) { - if (css.containsKey(resource)) - log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); - css.put(resource, ssRL); - } - - } - - } - - void loadFontsUrl(URL url) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { - String line = null; - while ((line = in.readLine()) != null) { - line = line.trim(); - if (!line.equals("") && !line.startsWith("#")) { - fonts.add(line); - } - } - } catch (IOException e) { - throw new CmsException("Cannot load URL " + url, e); - } - } - - void addResources(BundleResourceLoader themeBRL, String pattern) { - Bundle themeBundle = themeBRL.getBundle(); - Enumeration themeResources = themeBundle.findEntries(basePath, pattern, true); - if (themeResources == null) - return; - while (themeResources.hasMoreElements()) { - String resource = themeResources.nextElement().getPath(); - // remove first '/' so that RWT registers it - resource = resource.substring(1); - if (!resource.endsWith("/")) { - if (resources.containsKey(resource)) - log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); - resources.put(resource, themeBRL); - } - - } - - } - - public String getThemeId() { - return themeId; - } - - public void setThemeId(String themeId) { - this.themeId = themeId; - } - - public String getBasePath() { - return basePath; - } - - public void setBasePath(String basePath) { - this.basePath = basePath; - } - - public String getCssPath() { - return cssPath; - } - - public void setCssPath(String cssPath) { - this.cssPath = cssPath; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java index 3669db3fd..167d7341f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java @@ -14,7 +14,6 @@ import org.argeo.api.NodeUtils; import org.argeo.cms.CmsException; import org.argeo.cms.ui.CmsConstants; import org.argeo.cms.ui.CmsView; -import org.argeo.eclipse.ui.specific.UiContext; import org.argeo.jcr.JcrUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.service.ResourceManager; @@ -41,9 +40,13 @@ public class CmsUiUtils implements CmsConstants { /** * The CMS view related to this display, or null if none is available from this * call. + * + * @deprecated Use {@link CmsView#getCmsView(Composite)} instead. */ + @Deprecated public static CmsView getCmsView() { - return UiContext.getData(CmsView.KEY); +// return UiContext.getData(CmsView.class.getName()); + return CmsView.getCmsView(Display.getCurrent().getActiveShell()); } public static StringBuilder getServerBaseUrl(HttpServletRequest request) { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java index 50d844fcb..74c00b59f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java @@ -34,8 +34,10 @@ public class LoginEntryPoint implements EntryPoint, CmsView { @Override public int createUI() { final Display display = createDisplay(); - UiContext.setData(CmsView.KEY, this); +// UiContext.setData(CmsView.KEY, this); + CmsLoginShell loginShell = createCmsLoginShell(); + CmsView.registerCmsView(loginShell.getShell(), this); try { // try pre-auth loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, loginShell); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleApp.java index c5d86bc5f..149fff8ac 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleApp.java @@ -2,8 +2,10 @@ package org.argeo.cms.ui.util; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -26,6 +28,7 @@ import org.argeo.cms.CmsException; import org.argeo.cms.ui.CmsConstants; import org.argeo.cms.ui.CmsUiProvider; import org.argeo.cms.ui.LifeCycleUiProvider; +import org.argeo.cms.web.BundleResourceLoader; import org.argeo.cms.web.SimpleErgonomics; import org.argeo.jcr.JcrUtils; import org.eclipse.rap.rwt.RWT; @@ -97,7 +100,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration { Map defaultBranding = null; if (branding.containsKey("*")) defaultBranding = branding.get("*"); - String defaultTheme = defaultBranding.get(WebClient.THEME_ID); + // String defaultTheme = defaultBranding.get(WebClient.THEME_ID); // entry points for (String page : pages.keySet()) { @@ -109,7 +112,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration { // favicon if (properties.containsKey(WebClient.FAVICON)) { String themeId = defaultBranding.get(WebClient.THEME_ID); - Bundle themeBundle = ThemeUtils.findThemeBundle(bundleContext, themeId); + Bundle themeBundle = findThemeBundle(bundleContext, themeId); String faviconRelPath = properties.get(WebClient.FAVICON); application.addResource(faviconRelPath, new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle())); @@ -139,7 +142,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration { // stylesheets and themes Set themeBundles = new HashSet<>(); for (String themeId : styleSheets.keySet()) { - Bundle themeBundle = ThemeUtils.findThemeBundle(bundleContext, themeId); + Bundle themeBundle = findThemeBundle(bundleContext, themeId); StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader( themeBundle != null ? themeBundle : bundleContext.getBundle()); if (themeBundle != null) @@ -156,9 +159,9 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration { } for (Bundle themeBundle : themeBundles) { BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle); - ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.png"); - ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.gif"); - ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.jpg"); + SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.png"); + SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.gif"); + SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.jpg"); } } catch (RuntimeException e) { // Easier access to initialisation errors @@ -283,6 +286,41 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration { this.contextName = contextName; } + private static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL, + String pattern) { + Enumeration themeResources = themeBundle.findEntries("/", pattern, true); + if (themeResources == null) + return; + while (themeResources.hasMoreElements()) { + String resource = themeResources.nextElement().getPath(); + // remove first '/' so that RWT registers it + resource = resource.substring(1); + if (!resource.endsWith("/")) { + application.addResource(resource, themeBRL); + if (log.isTraceEnabled()) + log.trace("Registered " + resource + " from theme " + themeBundle); + } + + } + + } + + private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) { + if (themeId == null) + return null; + // TODO optimize + // TODO deal with multiple versions + Bundle themeBundle = null; + if (themeId != null) { + for (Bundle bundle : bundleContext.getBundles()) + if (themeId.equals(bundle.getSymbolicName())) { + themeBundle = bundle; + break; + } + } + return themeBundle; + } + class CmsExceptionHandler implements ExceptionHandler { @Override diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/ThemeUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/ThemeUtils.java deleted file mode 100644 index 90b684040..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/ThemeUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.net.URL; -import java.util.Enumeration; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.eclipse.rap.rwt.application.Application; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -public class ThemeUtils { - final static Log log = LogFactory.getLog(ThemeUtils.class); - - public static Bundle findThemeBundle(BundleContext bundleContext, String themeId) { - if (themeId == null) - return null; - // TODO optimize - // TODO deal with multiple versions - Bundle themeBundle = null; - if (themeId != null) { - for (Bundle bundle : bundleContext.getBundles()) - if (themeId.equals(bundle.getSymbolicName())) { - themeBundle = bundle; - break; - } - } - return themeBundle; - } - - public static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL, - String pattern) { - Enumeration themeResources = themeBundle.findEntries("/", pattern, true); - if (themeResources == null) - return; - while (themeResources.hasMoreElements()) { - String resource = themeResources.nextElement().getPath(); - // remove first '/' so that RWT registers it - resource = resource.substring(1); - if (!resource.endsWith("/")) { - application.addResource(resource, themeBRL); - if (log.isTraceEnabled()) - log.trace("Registered " + resource + " from theme " + themeBundle); - } - - } - - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/org.argeo.cms.ui/src/org/argeo/cms/web/AbstractCmsEntryPoint.java index 1c5d60bc4..bdc4f24bb 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/web/AbstractCmsEntryPoint.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/AbstractCmsEntryPoint.java @@ -118,7 +118,8 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement @Override protected final void createContents(final Composite parent) { - UiContext.setData(CmsView.KEY, this); + // UiContext.setData(CmsView.KEY, this); + CmsView.registerCmsView(parent.getShell(), this); Subject.doAs(getSubject(), new PrivilegedAction() { @Override public Void run() { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java similarity index 96% rename from org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleResourceLoader.java rename to org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java index 13df119b1..e6ad61db6 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleResourceLoader.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java @@ -1,4 +1,4 @@ -package org.argeo.cms.ui.util; +package org.argeo.cms.web; import java.io.IOException; import java.io.InputStream; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsThemeResourceLoader.java new file mode 100644 index 000000000..0cf9a7269 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsThemeResourceLoader.java @@ -0,0 +1,23 @@ +package org.argeo.cms.web; + +import java.io.IOException; +import java.io.InputStream; + +import org.argeo.cms.ui.CmsTheme; +import org.eclipse.rap.rwt.service.ResourceLoader; + +/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */ +public class CmsThemeResourceLoader implements ResourceLoader { + private final CmsTheme theme; + + public CmsThemeResourceLoader(CmsTheme theme) { + super(); + this.theme = theme; + } + + @Override + public InputStream getResourceAsStream(String resourceName) throws IOException { + return theme.getResourceAsStream(resourceName); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java index cae02fde5..4c55d4eab 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java @@ -1,18 +1,73 @@ package org.argeo.cms.web; +import java.util.Dictionary; +import java.util.HashMap; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.cms.ui.CmsApp; +import org.argeo.cms.ui.CmsAppListener; +import org.argeo.cms.ui.CmsTheme; +import org.argeo.util.LangUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.application.AbstractEntryPoint; import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.eclipse.rap.rwt.client.WebClient; import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; -public class CmsWebApp extends MinimalWebApp { +/** An RWT web app integrating with a {@link CmsApp}. */ +public class CmsWebApp implements ApplicationConfiguration, CmsAppListener { + private final static Log log = LogFactory.getLog(CmsWebApp.class); + + private BundleContext bundleContext; private CmsApp cmsApp; + private ServiceRegistration rwtAppReg; + + private final static String CONTEXT_NAME = "contextName"; + private String contextName; + + public void init(BundleContext bundleContext, Map properties) { + this.bundleContext = bundleContext; + contextName = properties.get(CONTEXT_NAME); +// registerIfAllThemesAvailable(); + } + + public void destroy(BundleContext bundleContext, Map properties) { + + } + @Override - protected void addEntryPoints(Application application, Map properties) { + public void configure(Application application) { for (String uiName : cmsApp.getUiNames()) { + CmsTheme theme = cmsApp.getTheme(uiName); + if (theme != null) + WebThemeUtils.apply(application, theme); + } +// for (CmsTheme theme : themes.values()) +// WebThemeUtils.apply(application, theme); + + Map properties = new HashMap<>(); + addEntryPoints(application, properties); + + } + + protected void addEntryPoints(Application application, Map commonProperties) { + for (String uiName : cmsApp.getUiNames()) { + Map properties = new HashMap<>(commonProperties); + CmsTheme theme = cmsApp.getTheme(uiName); + if (theme != null) { + properties.put(WebClient.THEME_ID, theme.getThemeId()); + properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders()); + } else { + properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); +// if (themeId != null) +// log.warn("Theme id " + themeId + " was specified but it was not found, using default RWT theme."); + } application.addEntryPoint("/" + uiName, () -> { return new AbstractEntryPoint() { private static final long serialVersionUID = -9153259126766694485L; @@ -27,12 +82,51 @@ public class CmsWebApp extends MinimalWebApp { } } +// private void registerIfAllThemesAvailable() { +// boolean themeMissing = false; +// uiNames: for (String uiName : cmsApp.getUiNames()) { +// String themeId = cmsApp.getThemeId(uiName); +// if (RWT.DEFAULT_THEME_ID.equals(themeId)) +// continue uiNames; +// if (!themes.containsKey(themeId)) { +// themeMissing = true; +// break uiNames; +// } +// } +// if (!themeMissing) { +// Dictionary regProps = LangUtils.dict(CONTEXT_NAME, contextName); +// if (bundleContext != null) { +// rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps); +// log.info("Published CMS web app /" + (contextName != null ? contextName : "")); +// } +// } +// } + public CmsApp getCmsApp() { return cmsApp; } - public void setCmsApp(CmsApp cmsApp) { + public void setCmsApp(CmsApp cmsApp, Map properties) { this.cmsApp = cmsApp; + this.cmsApp.addCmsAppListener(this); +// registerIfAllThemesAvailable(); + } + + public void unsetCmsApp(CmsApp cmsApp, Map properties) { + if (rwtAppReg != null) + rwtAppReg.unregister(); + this.cmsApp = null; + } + + @Override + public void themingUpdated() { + Dictionary regProps = LangUtils.dict(CONTEXT_NAME, contextName); + if (rwtAppReg != null) + rwtAppReg.unregister(); + if (bundleContext != null) { + rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps); + log.info("Published CMS web app /" + (contextName != null ? contextName : "")); + } } } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/MinimalWebApp.java b/org.argeo.cms.ui/src/org/argeo/cms/web/MinimalWebApp.java index fe8b20c92..188391c42 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/web/MinimalWebApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/MinimalWebApp.java @@ -1,11 +1,11 @@ package org.argeo.cms.web; -import static org.argeo.cms.ui.util.CmsTheme.CMS_THEME_BUNDLE_PROPERTY; +import static org.argeo.cms.ui.util.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY; import java.util.HashMap; import java.util.Map; -import org.argeo.cms.ui.util.CmsTheme; +import org.argeo.cms.ui.util.BundleCmsTheme; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.application.Application; import org.eclipse.rap.rwt.application.ApplicationConfiguration; @@ -15,12 +15,12 @@ import org.osgi.framework.BundleContext; /** Lightweight web app using only RWT and not the whole Eclipse platform. */ public class MinimalWebApp implements ApplicationConfiguration { - private CmsTheme theme; + private BundleCmsTheme theme; public void init(BundleContext bundleContext, Map properties) { if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) { String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString(); - theme = new CmsTheme(bundleContext, cmsThemeBundle); + theme = new BundleCmsTheme(bundleContext, cmsThemeBundle); } } @@ -36,16 +36,21 @@ public class MinimalWebApp implements ApplicationConfiguration { @Override public void configure(Application application) { if (theme != null) - theme.apply(application); + WebThemeUtils.apply(application, theme); Map properties = new HashMap<>(); if (theme != null) { properties.put(WebClient.THEME_ID, theme.getThemeId()); - properties.put(WebClient.HEAD_HTML, theme.getAdditionalHeaders()); + properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders()); } else { properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); } addEntryPoints(application, properties); } + + public void setTheme(BundleCmsTheme theme) { + this.theme = theme; + } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/WebThemeUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/web/WebThemeUtils.java new file mode 100644 index 000000000..a71ff97d3 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/WebThemeUtils.java @@ -0,0 +1,29 @@ +package org.argeo.cms.web; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.ui.CmsTheme; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.service.ResourceLoader; + +/** Web specific utilities around theming. */ +public class WebThemeUtils { + private final static Log log = LogFactory.getLog(WebThemeUtils.class); + + public static void apply(Application application, CmsTheme theme) { + ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme); + resources: for (String path : theme.getImagesPaths()) { + if (path.startsWith("target/")) + continue resources; // skip maven output + application.addResource(path, resourceLoader); + if (log.isTraceEnabled()) + log.trace("Theme " + theme.getThemeId() + ": added resource " + path); + } + for (String path : theme.getRapCssPaths()) { + application.addStyleSheet(theme.getThemeId(), path, resourceLoader); + if (log.isDebugEnabled()) + log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path); + } + } + +} diff --git a/org.argeo.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java index 968f8ffe1..779adb01e 100644 --- a/org.argeo.util/src/org/argeo/util/LangUtils.java +++ b/org.argeo.util/src/org/argeo/util/LangUtils.java @@ -40,23 +40,23 @@ public class LangUtils { */ /** - * Creates a new {@link Dictionary} with one key-value pair (neither key not - * value should be null) + * Creates a new {@link Dictionary} with one key-value pair. Key should not be + * null, but if the value is null, it returns an empty {@link Dictionary}. */ public static Dictionary dict(String key, Object value) { assert key != null; - assert value != null; Hashtable props = new Hashtable<>(); - props.put(key, value); + if (value != null) + props.put(key, value); return props; } - /**@deprecated Use {@link #dict(String, Object)} instead.*/ + /** @deprecated Use {@link #dict(String, Object)} instead. */ @Deprecated public static Dictionary dico(String key, Object value) { return dict(key, value); } - + /** Converts a {@link Dictionary} to a {@link Map} of strings. */ public static Map dictToStringMap(Dictionary properties) { if (properties == null) { -- 2.30.2