From: Mathieu Baudier Date: Mon, 12 Sep 2022 06:00:42 +0000 (+0200) Subject: Improve ACR editing support X-Git-Tag: v2.3.10~48 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=7282c1638941227f4e3fc8c0506ab0caf96ad1ea Improve ACR editing support --- diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java index 8d674f8a3..0a0ad0ea5 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java @@ -7,6 +7,8 @@ public interface ContentPart { Content getContent(); @Deprecated - Content getNode(); + default Content getNode() { + return getContent(); + } } 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 786b0d632..6608e749f 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 @@ -139,7 +139,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { else return Optional.empty(); } else - return null; + return Optional.empty(); } @Override diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java new file mode 100644 index 000000000..2427c7610 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java @@ -0,0 +1,337 @@ +package org.argeo.cms.swt.acr; + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsEditable; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.swt.SwtEditablePart; +import org.argeo.cms.swt.widgets.ScrolledPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.SAXParseException; + +/** Base class for viewers related to a page */ +public abstract class AbstractPageViewer { + + private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class); + + private final boolean readOnly; + /** The basis for the layouts, typically a ScrolledPage. */ + private final Composite page; + private final CmsEditable cmsEditable; + + private MouseListener mouseListener; + private FocusListener focusListener; + + private SwtEditablePart edited; +// private ISelection selection = StructuredSelection.EMPTY; + + private Subject viewerSubject; + + protected AbstractPageViewer(Composite parent, int style, CmsEditable cmsEditable) { + // read only at UI level + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + + this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable; +// if (this.cmsEditable instanceof Observable) +// ((Observable) this.cmsEditable).addObserver(this); + + if (cmsEditable.canEdit()) { + mouseListener = createMouseListener(); + focusListener = createFocusListener(); + } + page = findPage(parent); +// accessControlContext = AccessController.getContext(); + viewerSubject = CurrentUser.getCmsSession().getSubject(); + } + + public abstract Control getControl(); + +// /** +// * Can be called to simplify the called to isModelInitialized() and initModel() +// */ +// protected void initModelIfNeeded(Node node) { +// try { +// if (!isModelInitialized(node)) +// if (getCmsEditable().canEdit()) { +// initModel(node); +// node.getSession().save(); +// } +// } catch (RepositoryException e) { +// throw new JcrException("Cannot initialize model", e); +// } +// } +// +// /** Called if user can edit and model is not initialized */ +// protected Boolean isModelInitialized(Node node) throws RepositoryException { +// return true; +// } +// +// /** Called if user can edit and model is not initialized */ +// protected void initModel(Node node) throws RepositoryException { +// } + + /** Create (retrieve) the MouseListener to use. */ + protected MouseListener createMouseListener() { + return new MouseAdapter() { + private static final long serialVersionUID = 1L; + }; + } + + /** Create (retrieve) the FocusListener to use. */ + protected FocusListener createFocusListener() { + return new FocusListener() { + private static final long serialVersionUID = 1L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + } + }; + } + + protected Composite findPage(Composite composite) { + if (composite instanceof ScrolledPage) { + return (ScrolledPage) composite; + } else { + if (composite.getParent() == null) + return composite; + return findPage(composite.getParent()); + } + } + + public void layoutPage() { + if (page != null) + page.layout(true, true); + } + + protected void showControl(Control control) { + if (page != null && (page instanceof ScrolledPage)) + ((ScrolledPage) page).showControl(control); + } + +// @Override +// public void update(Observable o, Object arg) { +// if (o == cmsEditable) +// editingStateChanged(cmsEditable); +// } + + /** To be overridden in order to provide the actual refresh */ + protected void refresh(Control control) { + } + + /** To be overridden.Save the edited part. */ + protected void save(SwtEditablePart part) { + } + + /** Prepare the edited part */ + protected void prepare(SwtEditablePart part, Object caretPosition) { + } + + /** Notified when the editing state changed. Does nothing, to be overridden */ + protected void editingStateChanged(CmsEditable cmsEditable) { + } + + public void refresh() { + // TODO check actual context in order to notice a discrepancy + Subject viewerSubject = getViewerSubject(); + Subject.doAs(viewerSubject, (PrivilegedAction) () -> { + if (cmsEditable.canEdit() && !readOnly) + mouseListener = createMouseListener(); + else + mouseListener = null; + refresh(getControl()); + // layout(getControl()); + if (!getControl().isDisposed()) + layoutPage(); + return null; + }); + } + +// @Override +// public void setSelection(ISelection selection, boolean reveal) { +// this.selection = selection; +// } + + protected void updateContent(SwtEditablePart part) { + } + + // LOW LEVEL EDITION + protected void edit(SwtEditablePart part, Object caretPosition) { + if (edited == part) + return; + + if (edited != null && edited != part) { + SwtEditablePart previouslyEdited = edited; + try { + stopEditing(true); + } catch (Exception e) { + notifyEditionException(e); + edit(previouslyEdited, caretPosition); + return; + } + } + + part.startEditing(); + edited = part; + updateContent(part); + prepare(part, caretPosition); + edited.getControl().addFocusListener(new FocusListener() { + private static final long serialVersionUID = 6883521812717097017L; + + @Override + public void focusLost(FocusEvent event) { + stopEditing(true); + } + + @Override + public void focusGained(FocusEvent event) { + } + }); + + layout(part.getControl()); + showControl(part.getControl()); + } + + protected void stopEditing(Boolean save) { + if (edited instanceof Widget && ((Widget) edited).isDisposed()) { + edited = null; + return; + } + + assert edited != null; + if (edited == null) { + if (log.isTraceEnabled()) + log.warn("Told to stop editing while not editing anything"); + return; + } + + try { + if (save) + save(edited); + + edited.stopEditing(); + SwtEditablePart editablePart = edited; + Control control = ((SwtEditablePart) edited).getControl(); + edited = null; + // TODO make edited state management more robust + updateContent(editablePart); + layout(control); + } finally { + edited = null; + } + } + + // METHODS AVAILABLE TO EXTENDING CLASSES + protected void saveEdit() { + if (edited != null) + stopEditing(true); + } + + protected void cancelEdit() { + if (edited != null) + stopEditing(false); + } + + /** Layout this controls from the related base page. */ + public void layout(Control... controls) { + page.layout(controls); + } + + /** + * Find the first {@link SwtEditablePart} in the parents hierarchy of this + * control + */ + protected SwtEditablePart findDataParent(Control parent) { + if (parent instanceof SwtEditablePart) { + return (SwtEditablePart) parent; + } + if (parent.getParent() != null) + return findDataParent(parent.getParent()); + else + throw new IllegalStateException("No data parent found"); + } + + // UTILITIES + /** Check whether the edited part is in a proper state */ + protected void checkEdited() { + if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed()) + throw new IllegalStateException("Edited should not be null or disposed at this stage"); + } + + /** Persist all changes. */ + protected void persistChanges(ContentSession session) { +// session.save(); +// session.refresh(false); + // TODO notify that changes have been persisted + } + + /** Convenience method using a Node in order to save the underlying session. */ + protected void persistChanges(Content anyNode) { + persistChanges(((ProvidedContent) anyNode).getSession()); + } + + /** Notify edition exception */ + protected void notifyEditionException(Throwable e) { + Throwable eToLog = e; + if (e instanceof IllegalArgumentException) + if (e.getCause() instanceof SAXParseException) + eToLog = e.getCause(); + log.error(eToLog.getMessage(), eToLog); +// if (log.isTraceEnabled()) +// log.trace("Full stack of " + eToLog.getMessage(), e); + // TODO Light error notification popup + } + + protected Subject getViewerSubject() { + return viewerSubject; +// Subject res = null; +// if (accessControlContext != null) { +// res = Subject.getSubject(accessControlContext); +// } +// if (res == null) +// throw new IllegalStateException("No subject associated with this viewer"); +// return res; + } + + // GETTERS / SETTERS + public boolean isReadOnly() { + return readOnly; + } + + protected SwtEditablePart getEdited() { + return edited; + } + + public MouseListener getMouseListener() { + return mouseListener; + } + + public FocusListener getFocusListener() { + return focusListener; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + +// @Override +// public ISelection getSelection() { +// return selection; +// } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java index 89d003870..57c4da00c 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java @@ -11,7 +11,7 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -/** A structured UI related to a JCR context. */ +/** A structured UI related to an ACR context. */ public class SwtSection extends ContentComposite { private static final long serialVersionUID = -5933796173755739207L;