From 0bccba2b9f1ac5d0be1a06dcb4fe56859f641b9a Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 25 Jun 2023 10:37:59 +0200 Subject: [PATCH] Improve image management and ACR --- .../src/org/argeo/api/acr/Content.java | 14 +++- .../argeo/api/acr/spi/ProvidedContent.java | 17 +++-- .../src/org/argeo/api/cms/ux/Cms2DSize.java | 31 +-------- .../org/argeo/api/cms/ux/CmsImageManager.java | 3 +- .../argeo/cms/ux/AbstractImageManager.java | 18 +++--- .../src/org/argeo/cms/ux/CmsUxUtils.java | 2 +- .../org/argeo/cms/acr/CmsContentSession.java | 4 +- .../src/org/argeo/cms/acr/ContentUtils.java | 3 +- .../src/org/argeo/cms/acr/SvgAttrs.java | 4 ++ .../src/org/argeo/cms/acr/fs/FsContent.java | 2 +- .../src/org/argeo/cms/acr/xml/DomContent.java | 8 +-- .../argeo/cms/acr/xml/DomContentProvider.java | 3 +- .../cms/swt/AbstractSwtImageManager.java | 34 ++++++---- .../argeo/cms/swt/acr/AcrSwtImageManager.java | 26 +++++++- .../src/org/argeo/cms/swt/acr/Img.java | 47 ++++++++------ .../org/argeo/cms/swt/acr/LinkedControl.java | 64 +++++++++++++++++++ .../argeo/cms/swt/widgets/EditableImage.java | 7 +- 17 files changed, 189 insertions(+), 98 deletions(-) create mode 100644 swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/LinkedControl.java diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java index 865705f64..df5c149e6 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java @@ -16,6 +16,8 @@ import javax.xml.namespace.QName; * A semi-structured content, with attributes, within a hierarchical structure. */ public interface Content extends Iterable, Map { + /** The base of a repository path. */ + String ROOT_PATH = "/"; QName getName(); @@ -208,6 +210,14 @@ public interface Content extends Iterable, Map { return res; } + default List children(QNamed name) { + return children(name.qName()); + } + + default Optional soleChild(QNamed name) { + return soleChild(name.qName()); + } + default Optional soleChild(QName name) { List res = children(name); if (res.isEmpty()) @@ -270,9 +280,9 @@ public interface Content extends Iterable, Map { /** * A content within this repository * - * @param path either an abolute path or a path relative to this content + * @param path either an absolute path or a path relative to this content */ - Content getContent(String path); + Optional getContent(String path); /* * EXPERIMENTAL UNSUPPORTED diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java index d4162afb0..f1e5aaaa8 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java @@ -1,11 +1,11 @@ package org.argeo.api.acr.spi; +import java.util.Optional; + import org.argeo.api.acr.Content; /** A {@link Content} implementation. */ public interface ProvidedContent extends Content { - final static String ROOT_PATH = "/"; - /** The related {@link ProvidedSession}. */ ProvidedSession getSession(); @@ -39,15 +39,14 @@ public interface ProvidedContent extends Content { } @Override - default ProvidedContent getContent(String path) { - Content fileNode; - if (path.startsWith(ROOT_PATH)) {// absolute - fileNode = getSession().get(path); + default Optional getContent(String path) { + String absolutePath; + if (path.startsWith(Content.ROOT_PATH)) {// absolute + absolutePath = path; } else {// relative - String absolutePath = getPath() + '/' + path; - fileNode = getSession().get(absolutePath); + absolutePath = getPath() + '/' + path; } - return (ProvidedContent) fileNode; + return getSession().exists(absolutePath) ? Optional.of(getSession().get(absolutePath)) : Optional.empty(); } /* diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java index 1ec753a18..f35e98c50 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java @@ -1,38 +1,9 @@ package org.argeo.api.cms.ux; /** A 2D size. */ -public class Cms2DSize { - private Integer width; - private Integer height; - - public Cms2DSize() { - } - - public Cms2DSize(Integer width, Integer height) { - super(); - this.width = width; - this.height = height; - } - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - +public record Cms2DSize(int width, int height) { @Override public String toString() { return Cms2DSize.class.getSimpleName() + "[" + width + "," + height + "]"; } - } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java index 1ec54a9d9..4fb393a56 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java @@ -1,11 +1,12 @@ package org.argeo.api.cms.ux; import java.io.InputStream; +import java.net.URI; /** Read and write access to images. */ public interface CmsImageManager { /** Load image in control */ - public Boolean load(M node, V control, Cms2DSize size); + public Boolean load(M node, V control, Cms2DSize size, URI link); /** @return (0,0) if not available */ public Cms2DSize getImageSize(M node); diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java index 41c905ef6..31a0d6bed 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java @@ -10,16 +10,16 @@ public abstract class AbstractImageManager implements CmsImageManager implements CmsImageManager toPathSegments(String path) { List res = new ArrayList<>(); - if (EMPTY.equals(path) || ROOT_SLASH.equals(path)) + if (EMPTY.equals(path) || Content.ROOT_PATH.equals(path)) return res; collectPathSegments(path, res); return res; diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java index 61d8e0429..18527f590 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java @@ -2,6 +2,10 @@ package org.argeo.cms.acr; import org.argeo.api.acr.QNamed; +/** + * Some core SVG attributes, which are used to standardise generic concepts such + * as width, height, etc. + */ public enum SvgAttrs implements QNamed { /** */ width, diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java index 4b94abf1e..13b19aabb 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -80,7 +80,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { // TODO check file names with ':' ? if (isMountBase) { String mountPath = provider.getMountPath(); - if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) { + if (mountPath != null && !mountPath.equals(Content.ROOT_PATH)) { Content mountPoint = session.getMountPoint(mountPath); this.name = mountPoint.getName(); } else { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java index 22c157029..0686be7cb 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -69,7 +69,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { if (isLocalRoot()) {// root String mountPath = provider.getMountPath(); if (mountPath != null) { - if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + if (Content.ROOT_PATH.equals(mountPath)) { return CrName.root.qName(); } Content mountPoint = getSession().getMountPoint(mountPath); @@ -236,7 +236,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { String mountPath = provider.getMountPath(); if (mountPath == null) return null; - if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + if (Content.ROOT_PATH.equals(mountPath)) { return null; } String[] parent = ContentUtils.getParentPath(mountPath); @@ -386,7 +386,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { List res = new ArrayList<>(); if (isLocalRoot()) { String mountPath = provider.getMountPath(); - if (ContentUtils.SLASH_STRING.equals(mountPath)) {// repository root + if (Content.ROOT_PATH.equals(mountPath)) {// repository root res.add(CrName.root.qName()); } else { Content mountPoint = getSession().getMountPoint(mountPath); @@ -402,7 +402,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { public void addContentClasses(QName... contentClass) { if (isLocalRoot()) { String mountPath = provider.getMountPath(); - if (ContentUtils.SLASH_STRING.equals(mountPath)) {// repository root + if (Content.ROOT_PATH.equals(mountPath)) {// repository root throw new IllegalArgumentException("Cannot add content classes to repository root"); } else { Content mountPoint = getSession().getMountPoint(mountPath); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java index 39d9c2a90..a5abe8dd4 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java @@ -22,7 +22,6 @@ import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.cms.acr.CmsContentRepository; -import org.argeo.cms.acr.ContentUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -77,7 +76,7 @@ public class DomContentProvider implements ContentProvider, NamespaceContext { if (relativePath.startsWith("/")) throw new IllegalArgumentException("Relative path cannot start with /"); String xPathExpression = '/' + relativePath; - if (ContentUtils.SLASH_STRING.equals(mountPath)) // repository root + if (Content.ROOT_PATH.equals(mountPath)) // repository root xPathExpression = "/" + CrName.root.qName() + xPathExpression; try { NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET); diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java index 00a51ef92..30d6bc907 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java @@ -1,24 +1,29 @@ package org.argeo.cms.swt; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + import org.argeo.api.cms.ux.Cms2DSize; import org.argeo.cms.ux.AbstractImageManager; -import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; /** Manages only public images so far. */ public abstract class AbstractSwtImageManager extends AbstractImageManager { - protected abstract Image getSwtImage(M node); + protected abstract ImageData getSwtImageData(M node); protected abstract String noImg(Cms2DSize size); - public Boolean load(M node, Control control, Cms2DSize preferredSize) { + @Override + public Boolean load(M node, Control control, Cms2DSize preferredSize, URI link) { Cms2DSize imageSize = getImageSize(node); Cms2DSize size; String imgTag = null; - if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0 - || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) { - if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) { + if (preferredSize == null || imageSize.width() == 0 || imageSize.height() == 0 + || (preferredSize.width() == 0 && preferredSize.height() == 0)) { + if (imageSize.width() != 0 && imageSize.height() != 0) { // actual image size if completely known size = imageSize; } else { @@ -27,15 +32,15 @@ public abstract class AbstractSwtImageManager extends AbstractImageManager extends AbstractImageManager"); + sb.append(imgTag); + if (link != null) + sb.append(""); + lbl.setText(imgTag); // lbl.setSize(size); // } else if (control instanceof FileUpload) { @@ -70,8 +82,8 @@ public abstract class AbstractSwtImageManager extends AbstractImageManager { @Override @@ -24,8 +28,13 @@ public class AcrSwtImageManager extends AbstractSwtImageManager { } @Override - protected Image getSwtImage(Content node) { - throw new UnsupportedOperationException(); + protected ImageData getSwtImageData(Content node) { + try (InputStream in = node.open(InputStream.class)) { + ImageData imageData = new ImageData(in); + return imageData; + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override @@ -45,4 +54,15 @@ public class AcrSwtImageManager extends AbstractSwtImageManager { buf.append(node.getPath()); return buf.toString(); } + + @Override + public Cms2DSize getImageSize(Content node) { + // TODO cache it? + Optional width = node.get(SvgAttrs.width, Integer.class); + Optional height = node.get(SvgAttrs.height, Integer.class); + if (!width.isEmpty() && !height.isEmpty()) + return new Cms2DSize(width.get(), height.get()); + return super.getImageSize(node); + } + } 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 index a5d22bca1..578dac251 100644 --- 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 @@ -6,7 +6,6 @@ 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; @@ -14,38 +13,36 @@ import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; /** An image within the Argeo Text framework */ -public class Img extends EditableImage implements SwtSectionPart, ContentPart { +public class Img extends LinkedControl implements SwtSectionPart, ContentPart { private static final long serialVersionUID = 6233572783968188476L; private final SwtSection section; private final CmsImageManager imageManager; -// private FileUploadHandler currentUploadHandler = null; -// private FileUploadListener fileUploadListener; + + private Cms2DSize preferredImageSize; 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 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 imageManager) { - super(parent, swtStyle, preferredImageSize); + super(parent, swtStyle); + this.preferredImageSize = preferredImageSize; this.section = section; this.imageManager = imageManager != null ? imageManager : CmsSwtUtils.getCmsView(section).getImageManager(); -// CmsSwtUtils.style(this, TextStyles.TEXT_IMG); setData(imgNode); } @@ -58,20 +55,23 @@ public class Img extends EditableImage implements SwtSectionPart, ContentPart { } } - @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(); + boolean loaded = imageManager.load(imgNode, lbl, preferredImageSize, toUri()); return loaded; } + 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; + } + protected Content getUploadFolder() { return getContent().getParent(); } @@ -140,4 +140,15 @@ public class Img extends EditableImage implements SwtSectionPart, ContentPart { return "Img #" + getPartId(); } + public void setPreferredSize(Cms2DSize size) { + this.preferredImageSize = size; + } + + public Cms2DSize getPreferredImageSize() { + return preferredImageSize; + } + + public void setPreferredImageSize(Cms2DSize preferredImageSize) { + this.preferredImageSize = preferredImageSize; + } } diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/LinkedControl.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/LinkedControl.java new file mode 100644 index 000000000..6a75dfb2c --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/LinkedControl.java @@ -0,0 +1,64 @@ +package org.argeo.cms.swt.acr; + +import java.net.URI; + +import org.argeo.api.acr.Content; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.widgets.StyledControl; +import org.eclipse.swt.widgets.Composite; + +/** + * A {@link StyledControl} which can link either to an internal {@link Content} + * or an external URI. + */ +public abstract class LinkedControl extends StyledControl { + + private static final long serialVersionUID = -7603153425459801216L; + + private Content linkedContent; + private URI plainUri; + + public LinkedControl(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + public void setLink(Content linkedContent) { + if (plainUri != null) + throw new IllegalStateException("An URI is already set"); + this.linkedContent = linkedContent; + } + + public void setLink(URI uri) { + if (linkedContent != null) + throw new IllegalStateException("A linked content is already set"); + this.plainUri = uri; + } + + public boolean isInternalLink() { + if (!hasLink()) + throw new IllegalStateException("No link has been set"); + return linkedContent != null; + } + + public boolean hasLink() { + return plainUri != null || linkedContent != null; + } + + public Content getLinkedContent() { + return linkedContent; + } + + public URI getPlainUri() { + return plainUri; + } + + public URI toUri() { + if (plainUri != null) + return plainUri; + if (linkedContent != null) + return URI.create("#" + CmsSwtUtils.cleanPathForUrl(linkedContent.getPath())); + return null; + + } + +} 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 index e712e2fe6..1800a712a 100644 --- 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 @@ -12,6 +12,7 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; /** A stylable and editable image. */ +@Deprecated public abstract class EditableImage extends StyledControl { private static final long serialVersionUID = -5689145523114022890L; private final static CmsLog log = CmsLog.getLog(EditableImage.class); @@ -73,9 +74,9 @@ public abstract class EditableImage extends StyledControl { loaded = true; if (control != null) { ((Label) control).setText(imgTag); - control.setSize(preferredImageSize != null - ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight()) - : getSize()); + control.setSize( + preferredImageSize != null ? new Point(preferredImageSize.width(), preferredImageSize.height()) + : getSize()); } else { loaded = false; } -- 2.30.2