Improve CMS theming.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 4 Oct 2020 10:43:34 +0000 (12:43 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 4 Oct 2020 10:43:34 +0000 (12:43 +0200)
25 files changed:
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java
org.argeo.cms.ui/src/org/argeo/cms/ui/script/CmsScriptApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/script/Theme.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleResourceLoader.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsTheme.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/ThemeUtils.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/web/AbstractCmsEntryPoint.java
org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/web/CmsThemeResourceLoader.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java
org.argeo.cms.ui/src/org/argeo/cms/web/MinimalWebApp.java
org.argeo.cms.ui/src/org/argeo/cms/web/WebThemeUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/LangUtils.java

index f7a1ed667c8aee878928f453d6ffe5da4fd84024..acdbd07a34122d7ba3b86ab7282d59ec1f6b88c3 100644 (file)
@@ -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 (file)
index 0000000..9a1e41f
--- /dev/null
@@ -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<String, CmsTheme> themes = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
+
+       @Override
+       public Set<String> 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<String, String> properties) {
+               themes.put(theme.getThemeId(), theme);
+               if (allThemesAvailable())
+                       for (CmsAppListener listener : cmsAppListeners)
+                               listener.themingUpdated();
+       }
+
+       public void removeTheme(CmsTheme theme, Map<String, String> 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);
+       }
+
+}
index b8d382562352b84e3b53c58e8fe5753df1d241cd..dc5843142d681024a36e6aace18149565d888124 100644 (file)
@@ -9,4 +9,10 @@ public interface CmsApp {
        Set<String> 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 (file)
index 0000000..1fe3678
--- /dev/null
@@ -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 (file)
index 0000000..37f52a3
--- /dev/null
@@ -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
+        * <code>null</code> if not found
+        */
+       InputStream getResourceAsStream(String resourceName) throws IOException;
+
+       /** Relative paths to RAP specific CSS. */
+       Set<String> getRapCssPaths();
+
+       /** Relative paths to images such as icons. */
+       Set<String> getImagesPaths();
+
+       /** Tags to be added to the header section of the HTML page. */
+       String getHtmlHeaders();
+
+       /** The image registered at this path, or <code>null</code> 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 <code>null</code> if not found.
+        * 
+        * @param name          An icon file name without path and extension.
+        * @param preferredSize the preferred size, if <code>null</code>,
+        *                      {@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);
+       }
+
+}
index 6d70935d7e4a0ffd9d8205cac0404dc97fdbf2ff..ce0acb89fd2b68c0c2c249b6f9df362310d0483c 100644 (file)
@@ -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);
+       }
+
 }
index d3267004ec697d7b7fbbd549cad4ee915fef1eb7..3f0987199077a0550fcad4f5c74f18b6614f3473 100644 (file)
@@ -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<String> 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 (file)
index c053b47..0000000
+++ /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 <code>CmsTheme</code> 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 (file)
index 0000000..5ec931e
--- /dev/null
@@ -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<String, Image> imageCache = new HashMap<>();
+
+       private Map<String, Map<Integer, String>> 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<Integer, String> 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 (file)
index 0000000..1076b6c
--- /dev/null
@@ -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).<br>
+ * 
+ * Additional fonts listed in <code>/fonts.txt</code>.<br>
+ * Additional (standard CSS) header in <code>/header.css</code>.<br>
+ * RAP specific CSS files in <code>/rap/*.css</code>.<br>
+ * All images added as additional resources based on extensions
+ * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
+ */
+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<String> rapCssPaths = new TreeSet<>();
+       private Set<String> imagesPaths = new TreeSet<>();
+
+       private String headerCss;
+       private List<String> fonts = new ArrayList<>();
+
+       private String basePath;
+       private String rapCssPath;
+       private Bundle themeBundle;
+
+       public BundleCmsTheme() {
+
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               initResources(bundleContext, null);
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> 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("<style type='text/css'>\n");
+                       sb.append(headerCss);
+                       sb.append("\n</style>\n");
+               }
+               for (String link : fonts) {
+                       sb.append("<link rel='stylesheet' href='");
+                       sb.append(link);
+                       sb.append("'/>\n");
+               }
+               if (sb.length() == 0)
+                       return null;
+               else
+                       return sb.toString();
+       }
+
+       void addRapStyleSheets(Bundle themeBundle) {
+               Enumeration<URL> 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<URL> 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<String> getRapCssPaths() {
+               return rapCssPaths;
+       }
+
+       @Override
+       public Set<String> 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/BundleResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleResourceLoader.java
deleted file mode 100644 (file)
index 13df119..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
-       private final Bundle bundle;
-
-       public BundleResourceLoader(Bundle bundle) {
-               this.bundle = bundle;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               URL res = bundle.getEntry(resourceName);
-               if (res == null) {
-                       res = bundle.getResource(resourceName);
-                       if (res == null)
-                               throw new CmsException("Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
-               }
-               return res.openStream();
-       }
-
-       public Bundle getBundle() {
-               return bundle;
-       }
-
-}
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 (file)
index 0000000..33f13ad
--- /dev/null
@@ -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;
+       }
+}
index 79d5bb682299c33ef5cfa2c63949de32fe082129..daf78d47787b5e7545d94a0cf823cc939601fc9b 100644 (file)
@@ -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 (file)
index 1e1b586..0000000
+++ /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).<br>
- * 
- * Additional fonts listed in <code>/fonts.txt</code>.<br>
- * Additional (standard CSS) header in <code>/header.css</code>.<br>
- * RAP specific CSS files in <code>/rap/*.css</code>.<br>
- * All images added as additional resources based on extensions
- * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
- */
-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<String, ResourceLoader> css = new HashMap<>();
-       private Map<String, ResourceLoader> resources = new HashMap<>();
-
-       private String headerCss;
-       private List<String> 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("<style type='text/css'>\n");
-                       sb.append(headerCss);
-                       sb.append("\n</style>\n");
-               }
-               for (String link : fonts) {
-                       sb.append("<link rel='stylesheet' href='");
-                       sb.append(link);
-                       sb.append("'/>\n");
-               }
-               if (sb.length() == 0)
-                       return null;
-               else
-                       return sb.toString();
-       }
-
-       void addStyleSheets(Bundle themeBundle, ResourceLoader ssRL) {
-               Enumeration<URL> 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<URL> 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;
-       }
-
-}
index 3669db3fdb35ad9061f7ef993a5cf3415dae2fb5..167d7341f441748bf1e4bd99d4126572de794f84 100644 (file)
@@ -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) {
index 50d844fcb4c33424891703196b2235ca807533e7..74c00b59fc319a452b1b02dcd514852486ea53c1 100644 (file)
@@ -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);
index c5d86bc5f291333717c9ef768bc8da45e06566b3..149fff8acf1c81847ab0b83cd80b63aea1e5412b 100644 (file)
@@ -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<String, String> 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<Bundle> 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<URL> 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 (file)
index 90b6840..0000000
+++ /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<URL> 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);
-                       }
-
-               }
-
-       }
-
-}
index 1c5d60bc4750f59f11a266b592d92b36443f5cee..bdc4f24bbf4551111d90bf9cc89dd03caa3e8017 100644 (file)
@@ -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<Void>() {
                        @Override
                        public Void run() {
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/web/BundleResourceLoader.java
new file mode 100644 (file)
index 0000000..e6ad61d
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.argeo.cms.CmsException;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+
+/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
+public class BundleResourceLoader implements ResourceLoader {
+       private final Bundle bundle;
+
+       public BundleResourceLoader(Bundle bundle) {
+               this.bundle = bundle;
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               URL res = bundle.getEntry(resourceName);
+               if (res == null) {
+                       res = bundle.getResource(resourceName);
+                       if (res == null)
+                               throw new CmsException("Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
+               }
+               return res.openStream();
+       }
+
+       public Bundle getBundle() {
+               return bundle;
+       }
+
+}
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 (file)
index 0000000..0cf9a72
--- /dev/null
@@ -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);
+       }
+
+}
index cae02fde50784233d9d93277725ebf806d230903..4c55d4eabf11bd6ba837af4d00593b1458ff0494 100644 (file)
@@ -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<ApplicationConfiguration> rwtAppReg;
+
+       private final static String CONTEXT_NAME = "contextName";
+       private String contextName;
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               this.bundleContext = bundleContext;
+               contextName = properties.get(CONTEXT_NAME);
+//             registerIfAllThemesAvailable();
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
        @Override
-       protected void addEntryPoints(Application application, Map<String, String> 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<String, String> properties = new HashMap<>();
+               addEntryPoints(application, properties);
+
+       }
+
+       protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
+               for (String uiName : cmsApp.getUiNames()) {
+                       Map<String, String> 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<String, Object> 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<String, String> properties) {
                this.cmsApp = cmsApp;
+               this.cmsApp.addCmsAppListener(this);
+//             registerIfAllThemesAvailable();
+       }
+
+       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               if (rwtAppReg != null)
+                       rwtAppReg.unregister();
+               this.cmsApp = null;
+       }
+
+       @Override
+       public void themingUpdated() {
+               Dictionary<String, Object> 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 : ""));
+               }
        }
 
 }
index fe8b20c92097286b31e9dc63986ac50502f7dbbb..188391c42e68d4b736d444fc88e374a06d142946 100644 (file)
@@ -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<String, Object> 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<String, String> 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 (file)
index 0000000..a71ff97
--- /dev/null
@@ -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);
+               }
+       }
+
+}
index 968f8ffe1543335d1126d958d14b092f79d60305..779adb01eb4a204dd2dc07d27bee06be386d6083 100644 (file)
@@ -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<String, Object> dict(String key, Object value) {
                assert key != null;
-               assert value != null;
                Hashtable<String, Object> 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<String, Object> dico(String key, Object value) {
                return dict(key, value);
        }
-       
+
        /** Converts a {@link Dictionary} to a {@link Map} of strings. */
        public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
                if (properties == null) {