Improve ACR editing support
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 12 Sep 2022 06:00:42 +0000 (08:00 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 12 Sep 2022 06:00:42 +0000 (08:00 +0200)
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java

index 8d674f8a3e9137eb402735e7d39fbaf37475fcde..0a0ad0ea5e6eb2c81a96417bf92ed05f360e056a 100644 (file)
@@ -7,6 +7,8 @@ public interface ContentPart {
        Content getContent();
 
        @Deprecated
-       Content getNode();
+       default Content getNode() {
+               return getContent();
+       }
 
 }
index 786b0d63261f2b298cde63194176f9a3e7c629af..6608e749fce2aac3690f85481ffe84c9745908fa 100644 (file)
@@ -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 (file)
index 0000000..2427c76
--- /dev/null
@@ -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<Void>) () -> {
+                       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
index 89d003870d7fbf51256b3d304f58a0279165bac1..57c4da00ce0ea502442080051655c779d8d41d48 100644 (file)
@@ -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;