Improve ACR
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 14 Sep 2022 11:38:32 +0000 (13:38 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 14 Sep 2022 11:38:32 +0000 (13:38 +0200)
13 files changed:
jcr/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java
org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.util/src/org/argeo/util/naming/QNamed.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java [new file with mode: 0644]

index 7cc611c9b4f313b37adcb056d3a13b84996322e0..3522f1b423b3f3e2fc09422aa36e556c95188ad5 100644 (file)
@@ -154,23 +154,23 @@ public class CmsUiUtils {
                return noImg(AbstractImageManager.NO_IMAGE_SIZE);
        }
 
-       public static Image noImage(Cms2DSize size) {
-               ResourceManager rm = RWT.getResourceManager();
-               InputStream in = null;
-               try {
-                       in = rm.getRegisteredContent(AbstractImageManager.NO_IMAGE);
-                       ImageData id = new ImageData(in);
-                       ImageData scaled = id.scaledTo(size.getWidth(), size.getHeight());
-                       Image image = new Image(Display.getCurrent(), scaled);
-                       return image;
-               } finally {
-                       try {
-                               in.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-       }
+//     public static Image noImage(Cms2DSize size) {
+//             ResourceManager rm = RWT.getResourceManager();
+//             InputStream in = null;
+//             try {
+//                     in = rm.getRegisteredContent(AbstractImageManager.NO_IMAGE);
+//                     ImageData id = new ImageData(in);
+//                     ImageData scaled = id.scaledTo(size.getWidth(), size.getHeight());
+//                     Image image = new Image(Display.getCurrent(), scaled);
+//                     return image;
+//             } finally {
+//                     try {
+//                             in.close();
+//                     } catch (IOException e) {
+//                             // silent
+//                     }
+//             }
+//     }
 
        /** Lorem ipsum text to be used during development. */
        public final static String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
index 3cdf8d7dbec044f6a3a0c7a40fbe092b407a2b86..b605fa1e0062140b18fd5b1784fe8ea0fd6171bc 100644 (file)
@@ -1,14 +1,15 @@
 package org.argeo.api.acr;
 
+import static org.argeo.api.acr.NamespaceUtils.unqualified;
+
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.Collection;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
-import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 
 /**
@@ -28,29 +29,48 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
 
        <A> Optional<A> get(QName key, Class<A> clss);
 
-       default Object get(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> List<A> getMultiple(QName key, Class<A> clss);
+
+       /*
+        * ATTRIBUTES OPERATION HELPERS
+        */
+       default boolean containsKey(QNamed key) {
+               return containsKey(key.qName());
        }
 
-       default Object put(String key, Object value) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+       default <A> Optional<A> get(QNamed key, Class<A> clss) {
+               return get(key.qName(), clss);
        }
 
-       default Object remove(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       default Object get(QNamed key) {
+               return get(key.qName());
        }
 
-       Class<?> getType(QName key);
+       default Object put(QNamed key, Object value) {
+               return put(key.qName(), value);
+       }
 
-       boolean isMultiple(QName key);
+       default Object remove(QNamed key) {
+               return remove(key.qName());
+       }
+
+       // TODO do we really need the helpers below?
 
-       <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+       default Object get(String key) {
+               return get(unqualified(key));
+       }
+
+       default Object put(String key, Object value) {
+               return put(unqualified(key), value);
+       }
+
+       default Object remove(String key) {
+               return remove(unqualified(key));
+       }
 
        @SuppressWarnings("unchecked")
        default <A> List<A> getMultiple(QName key) {
@@ -60,14 +80,15 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                } catch (ClassCastException e) {
                        throw new IllegalArgumentException("Requested type is not the default type");
                }
-               Optional<List<A>> res = getMultiple(key, type);
-               if (res == null)
-                       return null;
-               else {
-                       if (res.isEmpty())
-                               throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
-                       return res.get();
-               }
+               List<A> res = getMultiple(key, type);
+               return res;
+//             if (res == null)
+//                     return null;
+//             else {
+//                     if (res.isEmpty())
+//                             throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+//                     return res.get();
+//             }
        }
 
        /*
@@ -83,9 +104,7 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
        Content add(QName name, QName... classes);
 
        default Content add(String name, QName... classes) {
-               if (name.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + name + " has a prefix");
-               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+               return add(unqualified(name), classes);
        }
 
        void remove();
@@ -119,6 +138,10 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return false;
        }
 
+       default boolean hasContentClass(QNamed contentClass) {
+               return hasContentClass(contentClass.qName());
+       }
+
        /*
         * SIBLINGS
         */
@@ -154,6 +177,10 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return false;
        }
 
+       default boolean hasChild(QNamed name) {
+               return hasChild(name.qName());
+       }
+
        default Content anyOrAddChild(QName name, QName... classes) {
                Content child = anyChild(name);
                if (child != null)
@@ -161,6 +188,10 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return this.add(name, classes);
        }
 
+       default Content anyOrAddChild(String name, QName... classes) {
+               return anyOrAddChild(unqualified(name), classes);
+       }
+
        /** Any child with this name, or null if there is none */
        default Content anyChild(QName name) {
                for (Content child : this) {
@@ -170,23 +201,49 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return null;
        }
 
-       /*
-        * CONVENIENCE METHODS
-        */
-       default String attr(String key) {
-               Object obj = get(key);
-               if (obj == null)
-                       return null;
-               return obj.toString();
+       default List<Content> children(QName name) {
+               List<Content> res = new ArrayList<>();
+               for (Content child : this) {
+                       if (child.getName().equals(name))
+                               res.add(child);
+               }
+               return res;
+       }
+
+       default Optional<Content> soleChild(QName name) {
+               List<Content> res = children(name);
+               if (res.isEmpty())
+                       return Optional.empty();
+               if (res.size() > 1)
+                       throw new IllegalStateException(this + " has multiple children with name " + name);
+               return Optional.of(res.get(0));
+       }
+
+       default Content child(QName name) {
+               return soleChild(name).orElseThrow();
+       }
 
+       default Content child(QNamed name) {
+               return child(name.qName());
        }
 
+       /*
+        * ATTR AS STRING
+        */
        default String attr(QName key) {
+               // TODO check String type?
                Object obj = get(key);
                if (obj == null)
                        return null;
                return obj.toString();
+       }
 
+       default String attr(QNamed key) {
+               return attr(key.qName());
+       }
+
+       default String attr(String key) {
+               return attr(unqualified(key));
        }
 //
 //     default String attr(Object key) {
index 64566ea0c9dbffe785e7cf34248105a82d0ae659..df582868b349798da88d124264d5f846c432feec 100644 (file)
@@ -65,6 +65,17 @@ public class NamespaceUtils {
                return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI);
        }
 
+       public static void checkNoPrefix(String unqualified) {
+               if (unqualified.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + unqualified + " has a prefix");
+       }
+
+       public static QName unqualified(String name) {
+               checkNoPrefix(name);
+               return new ContentName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX);
+
+       }
+
        /*
         * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
         */
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java b/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java
new file mode 100644 (file)
index 0000000..063a7d3
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.api.acr;
+
+import java.util.function.Supplier;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/** An optionally qualified name. Primarily meant to be used in enums. */
+public interface QNamed extends Supplier<String> {
+       String name();
+
+       /** To be overridden when XML naming is not compatible with Java naming. */
+       default String localName() {
+               return name();
+       }
+
+       default QName qName() {
+               return new ContentName(getNamespace(), localName(), getDefaultPrefix());
+       }
+
+       default String get(NamespaceContext namespaceContext) {
+               return namespaceContext.getPrefix(getNamespace()) + ":" + localName();
+       }
+
+       default String get() {
+               return getDefaultPrefix() + ":" + localName();
+       }
+
+       String getNamespace();
+
+       String getDefaultPrefix();
+
+       /** To be used by enums without namespace (typically XML attributes). */
+       static interface Unqualified extends QNamed {
+               @Override
+               default String getNamespace() {
+                       return XMLConstants.NULL_NS_URI;
+               }
+
+               @Override
+               default String getDefaultPrefix() {
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               }
+
+       }
+}
index d9f378329f59a3d8932910cf2328df97f4d27796..e2807c0efd19e746c7a9ab41a2971e36c1fe55e9 100644 (file)
@@ -4,6 +4,8 @@ import org.argeo.api.acr.Content;
 
 /** A {@link Content} implementation. */
 public interface ProvidedContent extends Content {
+       final static String ROOT_PATH = "/";
+
        ProvidedSession getSession();
 
        ContentProvider getProvider();
@@ -19,4 +21,15 @@ public interface ProvidedContent extends Content {
        default ProvidedContent getMountPoint(String relativePath) {
                throw new UnsupportedOperationException("This content doe not support mount");
        }
+
+       default ProvidedContent getContent(String path) {
+               Content fileNode;
+               if (path.startsWith(ROOT_PATH)) {// absolute
+                       fileNode = getSession().get(path);
+               } else {// relative
+                       String absolutePath = getPath() + '/' + path;
+                       fileNode = getSession().get(absolutePath);
+               }
+               return (ProvidedContent) fileNode;
+       }
 }
index 9667e8352f977576065a6ddbb7f18f6164498cc9..1ec753a187770d4d6f67c02476754dadc6a05a9e 100644 (file)
@@ -6,7 +6,6 @@ public class Cms2DSize {
        private Integer height;
 
        public Cms2DSize() {
-
        }
 
        public Cms2DSize(Integer width, Integer height) {
@@ -31,4 +30,9 @@ public class Cms2DSize {
                this.height = height;
        }
 
+       @Override
+       public String toString() {
+               return Cms2DSize.class.getSimpleName() + "[" + width + "," + height + "]";
+       }
+
 }
index 1cffef40ef5cc77f5be0783bfd5087ba48c1a565..ce05dc14c904e41ee5279725f864c346d7d0de94 100644 (file)
@@ -55,34 +55,27 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
 
        @SuppressWarnings("unchecked")
        @Override
-       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
+       public <A> List<A> getMultiple(QName key, Class<A> clss) {
                Object value = get(key);
                if (value == null)
-                       return Optional.empty();
+                       return new ArrayList<>();
                if (value instanceof List) {
-                       try {
-                               List<A> res = (List<A>) value;
-                               return Optional.of(res);
-                       } catch (ClassCastException e) {
-                               List<A> res = new ArrayList<>();
-                               List<?> lst = (List<?>) value;
-                               try {
-                                       for (Object o : lst) {
-                                               A item = (A) o;
-                                               res.add(item);
-                                       }
-                                       return Optional.of(res);
-                               } catch (ClassCastException e1) {
-                                       return Optional.empty();
-                               }
+                       if (isDefaultAttrTypeRequested(clss))
+                               return (List<A>) value;
+                       List<A> res = new ArrayList<>();
+                       List<?> lst = (List<?>) value;
+                       for (Object o : lst) {
+                               A item = clss.isAssignableFrom(String.class) ? (A) o.toString() : (A) o;
+                               res.add(item);
                        }
+                       return res;
                } else {// singleton
-                       try {
-                               A res = (A) value;
-                               return Optional.of(Collections.singletonList(res));
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
+//                     try {
+                       A res = (A) value;
+                       return Collections.singletonList(res);
+//                     } catch (ClassCastException e) {
+//                             return Optional.empty();
+//                     }
                }
        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java
new file mode 100644 (file)
index 0000000..0d38387
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.cms.acr;
+
+import org.argeo.api.acr.QNamed;
+
+public enum SvgAttrs implements QNamed {
+       /** */
+       width,
+       /** */
+       height,
+       /** */
+       length,
+       /** */
+       unit,
+       /** */
+       dur,
+       /** */
+       direction,
+       //
+       ;
+
+       @Override
+       public String getNamespace() {
+               return CmsContentTypes.SVG_1_1.getNamespace();
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return CmsContentTypes.SVG_1_1.getDefaultPrefix();
+       }
+
+}
index d61827d500eb03e9cebc1755d6266072ebc6ed18..b8f98d2c84996e7fa4c6b1ad39e465037298b076 100644 (file)
@@ -33,17 +33,21 @@ import javax.xml.transform.stream.StreamResult;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrAttributeType;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.acr.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.util.FsUtils;
 
 /** Content persisted as a filesystem {@link Path}. */
 public class FsContent extends AbstractContent implements ProvidedContent {
+       private CmsLog log = CmsLog.getLog(FsContent.class);
+
        final static String USER_ = "user:";
 
        private static final Map<QName, String> BASIC_KEYS;
@@ -146,25 +150,41 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        }
                        // TODO perform trivial file conversion to other formats
                }
+
+               // TODO better deal with multiple types
                if (value instanceof byte[]) {
                        String str = new String((byte[]) value, StandardCharsets.UTF_8);
                        String[] arr = str.split("\n");
+
                        if (arr.length == 1) {
-                               res = (A) arr[0];
+                               if (clss.isAssignableFrom(String.class)) {
+                                       res = (A) arr[0];
+                               } else {
+                                       res = (A) CrAttributeType.parse(arr[0]);
+                               }
                        } else {
-                               List<String> lst = new ArrayList<>();
+                               List<Object> lst = new ArrayList<>();
                                for (String s : arr) {
-                                       lst.add(s);
+                                       lst.add(CrAttributeType.parse(s));
                                }
                                res = (A) lst;
                        }
                }
-               if (res == null)
-                       try {
-                               res = (A) value;
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
+               if (res == null) {
+                       if (isDefaultAttrTypeRequested(clss))
+                               return Optional.of((A) CrAttributeType.parse(value.toString()));
+                       if (clss.isAssignableFrom(value.getClass()))
+                               return Optional.of((A) value);
+                       if (clss.isAssignableFrom(String.class))
+                               return Optional.of((A) value.toString());
+                       log.warn("Cannot interpret " + key + " in " + this);
+                       return Optional.empty();
+//                     try {
+//                             res = (A) value;
+//                     } catch (ClassCastException e) {
+//                             return Optional.empty();
+//                     }
+               }
                return Optional.of(res);
        }
 
@@ -337,12 +357,10 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        @Override
        public List<QName> getContentClasses() {
                List<QName> res = new ArrayList<>();
-               Optional<List<String>> value = getMultiple(CrName.cc.qName(), String.class);
-               if (!value.isEmpty()) {
-                       for (String s : value.get()) {
-                               QName name = NamespaceUtils.parsePrefixedName(provider, s);
-                               res.add(name);
-                       }
+               List<String> value = getMultiple(CrName.cc.qName(), String.class);
+               for (String s : value) {
+                       QName name = NamespaceUtils.parsePrefixedName(provider, s);
+                       res.add(name);
                }
                if (Files.isDirectory(path))
                        res.add(CrName.collection.qName());
index 9eac3607a02494a810094dfc30bd79019dc0c431..bcbb4742a17881aa0f9415a6f87080b98424aec3 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.util.naming;
 import javax.xml.namespace.QName;
 
 /** A (possibly) qualified name. To be used in enums. */
+@Deprecated
 public interface QNamed {
        String name();
 
index 5e83454dec6607fe9edd3c5181b1494c8115f614..e683d9630610a5d97e37eef59b2768e082357eed 100644 (file)
@@ -3,35 +3,46 @@ package org.argeo.cms.swt.acr;
 import java.io.InputStream;
 
 import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.ux.Cms2DSize;
 import org.argeo.cms.swt.AbstractSwtImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.CmsUxUtils;
 import org.eclipse.swt.graphics.Image;
 
 public class AcrSwtImageManager extends AbstractSwtImageManager<Content> {
 
        @Override
        public String getImageUrl(Content node) {
-               // TODO Auto-generated method stub
-               return null;
+               return getDataPathForUrl(node);
        }
 
        @Override
        public String uploadImage(Content context, Content uploadFolder, String fileName, InputStream in,
                        String contentType) {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        @Override
        protected Image getSwtImage(Content node) {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        @Override
        protected String noImg(Cms2DSize size) {
-               // TODO Auto-generated method stub
-               return null;
+               String dataPath = "";
+               return CmsUxUtils.img(dataPath, size);
        }
 
+       protected String getDataPathForUrl(Content content) {
+               return CmsSwtUtils.cleanPathForUrl(getDataPath(content));
+       }
+
+       /** A path in the node repository */
+       protected String getDataPath(Content node) {
+               // TODO make it more configurable?
+               StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
+               buf.append(node.getPath());
+               return buf.toString();
+       }
 }
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java
new file mode 100644 (file)
index 0000000..eb52fc6
--- /dev/null
@@ -0,0 +1,144 @@
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.EditableImage;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.eclipse.ui.specific.CmsFileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An image within the Argeo Text framework */
+public class Img extends EditableImage implements SwtSectionPart, ContentPart {
+       private static final long serialVersionUID = 6233572783968188476L;
+
+       private final SwtSection section;
+
+       private final CmsImageManager<Control, Content> imageManager;
+//     private FileUploadHandler currentUploadHandler = null;
+//     private FileUploadListener fileUploadListener;
+
+       public Img(Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       public Img(Composite parent, int swtStyle, Content imgNode) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, null);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       public Img(Composite parent, int swtStyle, Content imgNode, CmsImageManager<Control, Content> imageManager) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       Img(SwtSection section, Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize,
+                       CmsImageManager<Control, Content> imageManager) {
+               super(parent, swtStyle, preferredImageSize);
+               this.section = section;
+               this.imageManager = imageManager != null ? imageManager
+                               : (CmsImageManager<Control, Content>) CmsSwtUtils.getCmsView(section).getImageManager();
+//             CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
+               setData(imgNode);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               if (isEditing()) {
+                       return createImageChooser(box, style);
+               } else {
+                       return createLabel(box, style);
+               }
+       }
+
+       @Override
+       public synchronized void stopEditing() {
+               super.stopEditing();
+//             fileUploadListener = null;
+       }
+
+       @Override
+       protected synchronized Boolean load(Control lbl) {
+               Content imgNode = getContent();
+               boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
+               // getParent().layout();
+               return loaded;
+       }
+
+       protected Content getUploadFolder() {
+               return getContent().getParent();
+       }
+
+       protected String getUploadName() {
+               Content node = getContent();
+               // TODO centralise pattern?
+               return NamespaceUtils.toPrefixedName(node.getName()) + '[' + node.getSiblingIndex() + ']';
+       }
+
+       protected CmsImageManager<Control, Content> getImageManager() {
+               return imageManager;
+       }
+
+       protected Control createImageChooser(Composite box, String style) {
+//             JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
+//                             imageManager);
+//             if (currentUploadHandler != null)
+//                     currentUploadHandler.dispose();
+//             currentUploadHandler = prepareUpload(receiver);
+//             final ServerPushSession pushSession = new ServerPushSession();
+               final CmsFileUpload fileUpload = new CmsFileUpload(box, SWT.NONE);
+               CmsSwtUtils.style(fileUpload, style);
+               fileUpload.addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = -9158471843941668562L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+//                             pushSession.start();
+//                             fileUpload.submit(currentUploadHandler.getUploadUrl());
+                       }
+               });
+               return fileUpload;
+       }
+
+//     protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+//             final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
+//             if (fileUploadListener != null)
+//                     uploadHandler.addUploadListener(fileUploadListener);
+//             return uploadHandler;
+//     }
+
+       @Override
+       public SwtSection getSection() {
+               return section;
+       }
+
+//     public void setFileUploadListener(FileUploadListener fileUploadListener) {
+//             this.fileUploadListener = fileUploadListener;
+//             if (currentUploadHandler != null)
+//                     currentUploadHandler.addUploadListener(fileUploadListener);
+//     }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       @Override
+       public String getPartId() {
+               return ((ProvidedContent) getContent()).getSessionLocalId();
+       }
+
+       @Override
+       public String toString() {
+               return "Img #" + getPartId();
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java
new file mode 100644 (file)
index 0000000..e712e2f
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.AbstractImageManager;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** A stylable and editable image. */
+public abstract class EditableImage extends StyledControl {
+       private static final long serialVersionUID = -5689145523114022890L;
+       private final static CmsLog log = CmsLog.getLog(EditableImage.class);
+
+       private Cms2DSize preferredImageSize;
+       private Boolean loaded = false;
+
+       public EditableImage(Composite parent, int swtStyle) {
+               super(parent, swtStyle);
+       }
+
+       public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) {
+               super(parent, swtStyle);
+               this.preferredImageSize = preferredImageSize;
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               // composite.setLayoutData(fillWidth());
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               // control.setLayoutData(fillWidth());
+       }
+
+       /** To be overriden. */
+       protected String createImgTag() {
+               return noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y));
+       }
+
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = new Label(box, getStyle());
+               // lbl.setLayoutData(CmsUiUtils.fillWidth());
+               CmsSwtUtils.markup(lbl);
+               CmsSwtUtils.style(lbl, style);
+               if (mouseListener != null)
+                       lbl.addMouseListener(mouseListener);
+               load(lbl);
+               return lbl;
+       }
+
+       /** To be overriden. */
+       protected synchronized Boolean load(Control control) {
+               String imgTag;
+               try {
+                       imgTag = createImgTag();
+               } catch (Exception e) {
+                       // throw new CmsException("Cannot retrieve image", e);
+                       log.error("Cannot retrieve image", e);
+                       imgTag = noImg(preferredImageSize);
+                       loaded = false;
+               }
+
+               if (imgTag == null) {
+                       loaded = false;
+                       imgTag = noImg(preferredImageSize);
+               } else
+                       loaded = true;
+               if (control != null) {
+                       ((Label) control).setText(imgTag);
+                       control.setSize(preferredImageSize != null
+                                       ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight())
+                                       : getSize());
+               } else {
+                       loaded = false;
+               }
+               getParent().layout();
+               return loaded;
+       }
+
+       public void setPreferredSize(Cms2DSize size) {
+               this.preferredImageSize = size;
+               if (!loaded) {
+                       load((Label) getControl());
+               }
+       }
+
+       protected Text createText(Composite box, String style) {
+               Text text = new Text(box, getStyle());
+               CmsSwtUtils.style(text, style);
+               return text;
+       }
+
+       public Cms2DSize getPreferredImageSize() {
+               return preferredImageSize;
+       }
+
+       public static String noImg(Cms2DSize size) {
+//             ResourceManager rm = RWT.getResourceManager();
+//             String noImgPath=rm.getLocation(AbstractImageManager.NO_IMAGE);
+               // FIXME load it via package service
+               String noImgPath = "";
+               return CmsUxUtils.img(noImgPath, size);
+       }
+
+       public static String noImg() {
+               return noImg(AbstractImageManager.NO_IMAGE_SIZE);
+       }
+
+}