Introduce guided form to replace wizards
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 20 Sep 2022 06:39:05 +0000 (08:39 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 20 Sep 2022 06:39:05 +0000 (08:39 +0200)
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java

diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java
new file mode 100644 (file)
index 0000000..f1e89f1
--- /dev/null
@@ -0,0 +1,86 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractGuidedForm implements GuidedForm {
+       private String formTitle;
+       private List<Page> pages = new ArrayList<>();
+       private View view;
+
+       @Override
+       public abstract void addPages();
+
+       public void addPage(AbstractGuidedFormPage page) {
+               page.setView(view);
+               pages.add(page);
+       }
+
+       @Override
+       public boolean canFinish() {
+               return false;
+       }
+
+       @Override
+       public boolean performFinish() {
+               return false;
+       }
+
+       @Override
+       public boolean performCancel() {
+               return false;
+       }
+
+       @Override
+       public int getPageCount() {
+               return pages.size();
+       }
+
+       @Override
+       public List<Page> getPages() {
+               return Collections.unmodifiableList(pages);
+       }
+
+       @Override
+       public Page getStartingPage() {
+               if (pages.isEmpty())
+                       throw new IllegalStateException("No page available");
+               return pages.get(0);
+       }
+
+       @Override
+       public Page getPreviousPage(Page page) {
+               int index = pages.indexOf(page);
+               if (index == 0 || index == -1) {
+                       // first page or page not found
+                       return null;
+               }
+               return pages.get(index - 1);
+       }
+
+       @Override
+       public Page getNextPage(Page page) {
+               int index = pages.indexOf(page);
+               if (index == pages.size() - 1 || index == -1) {
+                       // last page or page not found
+                       return null;
+               }
+               return pages.get(index + 1);
+       }
+
+       public void setFormTitle(String formTitle) {
+               this.formTitle = formTitle;
+       }
+
+       @Override
+       public String getFormTitle() {
+               return formTitle;
+       }
+
+       @Override
+       public void setView(View view) {
+               this.view = view;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java
new file mode 100644 (file)
index 0000000..d565510
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.cms.ux.widgets;
+
+import org.argeo.cms.ux.widgets.GuidedForm.View;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+
+public class AbstractGuidedFormPage implements Page {
+       private String pageName;
+       private String title;
+       private View view;
+
+       public AbstractGuidedFormPage(String pageName) {
+               super();
+               this.pageName = pageName;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+       }
+
+       public void setView(View container) {
+               this.view = container;
+
+       }
+
+       public String getPageName() {
+               return pageName;
+       }
+
+       @Override
+       public String getTitle() {
+               return title;
+       }
+
+       public View getView() {
+               return view;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java
new file mode 100644 (file)
index 0000000..de8554e
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.List;
+
+public interface GuidedForm {
+       String getFormTitle();
+
+       boolean canFinish();
+
+       boolean performFinish();
+
+       boolean performCancel();
+
+       void addPages();
+
+       int getPageCount();
+
+       List<Page> getPages();
+
+       Page getStartingPage();
+
+       Page getPreviousPage(Page page);
+
+       Page getNextPage(Page page);
+
+       void setView(View view);
+
+       interface Page {
+
+               default boolean canFlipToNextPage() {
+                       return true;
+               }
+
+               default String getMessage() {
+                       return null;
+               }
+
+               String getTitle();
+
+       }
+
+       interface View {
+               void updateButtons();
+       }
+}
index ff752c8cafe8d417c08e6995ee8d5822fe86f922..ddb6e1b33b0c12271d92d3ef573075ff11d45085 100644 (file)
@@ -28,7 +28,7 @@ public abstract class AbstractSwtCmsView implements CmsView {
 
        protected LoginContext loginContext;
        protected String state;
-       protected Throwable exception;
+//     protected Throwable exception;
        protected UxContext uxContext;
        protected CmsImageManager imageManager;
 
index a01c919e939a1ada8727d3630883acde5ea6c31b..ddb8a7f95ff438067fb3242be261ebf64810e85e 100644 (file)
@@ -23,19 +23,19 @@ public class CmsFeedback extends LightweightDialog {
        private String message;
        private Throwable exception;
 
-       public CmsFeedback(Shell parentShell, String message, Throwable e) {
+       private CmsFeedback(Shell parentShell, String message, Throwable e) {
                super(parentShell);
                this.message = message;
                this.exception = e;
-               log.error(message, e);
        }
 
-       public static CmsFeedback show(String message, Throwable e) {
+       public static CmsFeedback error(String message, Throwable e) {
                // rethrow ThreaDeath in order to make sure that RAP will properly clean
                // up the UI thread
                if (e instanceof ThreadDeath)
                        throw (ThreadDeath) e;
 
+               log.error(message, e);
                try {
                        CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
                        cmsFeedback.setBlockOnOpen(false);
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java
new file mode 100644 (file)
index 0000000..a7242b9
--- /dev/null
@@ -0,0 +1,206 @@
+package org.argeo.cms.swt.widgets;
+
+import java.util.List;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ux.widgets.GuidedForm;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** A wizard dialog based on {@link LightweightDialog}. */
+public class SwtGuidedFormDialog extends LightweightDialog implements GuidedForm.View {
+       private GuidedForm guidedForm;
+       private GuidedForm.Page currentPage;
+       private int currentPageIndex;
+
+       private Label titleBar;
+       private Label message;
+       private Composite[] pageBodies;
+       private Composite buttons;
+       private Button back;
+       private Button next;
+       private Button finish;
+
+       public SwtGuidedFormDialog(Shell parentShell, GuidedForm guidedForm) {
+               super(parentShell);
+               this.guidedForm = guidedForm;
+               guidedForm.setView(this);
+               // create the pages
+               guidedForm.addPages();
+               for (Page page : guidedForm.getPages()) {
+                       if (!(page instanceof SwtGuidedFormPage))
+                               throw new IllegalArgumentException("Pages form must implement " + SwtGuidedFormPage.class);
+               }
+               currentPage = guidedForm.getStartingPage();
+               if (currentPage == null)
+                       throw new IllegalArgumentException("At least one wizard page is required");
+       }
+
+       @Override
+       protected Control createDialogArea(Composite parent) {
+               updateWindowTitle();
+
+               Composite messageArea = new Composite(parent, SWT.NONE);
+               messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               {
+                       messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+                       titleBar = new Label(messageArea, SWT.WRAP);
+                       titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
+                       titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
+                       updateTitleBar();
+                       Button cancelButton = new Button(messageArea, SWT.FLAT);
+                       cancelButton.setText(CmsMsg.cancel.lead());
+                       cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
+                       cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL));
+                       message = new Label(messageArea, SWT.WRAP);
+                       message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
+                       updateMessage();
+               }
+
+               Composite body = new Composite(parent, SWT.BORDER);
+               body.setLayout(new FormLayout());
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               pageBodies = new Composite[guidedForm.getPageCount()];
+               List<GuidedForm.Page> pages = guidedForm.getPages();
+               for (int i = 0; i < pages.size(); i++) {
+                       pageBodies[i] = new Composite(body, SWT.NONE);
+                       pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       setSwitchingFormData(pageBodies[i]);
+                       // !! SWT specific
+                       SwtGuidedFormPage page = (SwtGuidedFormPage) pages.get(i);
+                       page.createControl(pageBodies[i]);
+               }
+               showPage(currentPage);
+
+               buttons = new Composite(parent, SWT.NONE);
+               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               {
+                       boolean singlePage = guidedForm.getPageCount() == 1;
+                       // singlePage = false;// dev
+                       GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
+                       layout.marginWidth = 0;
+                       layout.marginHeight = 0;
+                       buttons.setLayout(layout);
+                       // TODO revert order for right-to-left languages
+
+                       if (!singlePage) {
+                               back = new Button(buttons, SWT.PUSH);
+                               back.setText(CmsMsg.wizardBack.lead());
+                               back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               back.addSelectionListener((Selected) (e) -> backPressed());
+
+                               next = new Button(buttons, SWT.PUSH);
+                               next.setText(CmsMsg.wizardNext.lead());
+                               next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               next.addSelectionListener((Selected) (e) -> nextPressed());
+                       }
+                       finish = new Button(buttons, SWT.PUSH);
+                       finish.setText(CmsMsg.wizardFinish.lead());
+                       finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       finish.addSelectionListener((Selected) (e) -> finishPressed());
+
+                       updateButtons();
+               }
+               return body;
+       }
+
+       public GuidedForm.Page getCurrentPage() {
+               return currentPage;
+       }
+
+       public Shell getShell() {
+               return getForegoundShell();
+       }
+
+       public void showPage(GuidedForm.Page page) {
+               List<GuidedForm.Page> pages = guidedForm.getPages();
+               int index = -1;
+               for (int i = 0; i < pages.size(); i++) {
+                       if (page == pages.get(i)) {
+                               index = i;
+                               break;
+                       }
+               }
+               if (index < 0)
+                       throw new IllegalArgumentException("Cannot find index of wizard page " + page);
+               pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
+
+               // // clear
+               // for (Control c : body.getChildren())
+               // c.dispose();
+               // page.createControl(body);
+               // body.layout(true, true);
+               currentPageIndex = index;
+               currentPage = page;
+       }
+
+       @Override
+       public void updateButtons() {
+               if (back != null)
+                       back.setEnabled(guidedForm.getPreviousPage(currentPage) != null);
+               if (next != null)
+                       next.setEnabled(guidedForm.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
+               if (finish != null) {
+                       finish.setEnabled(guidedForm.canFinish());
+               }
+       }
+
+       public void updateMessage() {
+               if (currentPage.getMessage() != null)
+                       message.setText(currentPage.getMessage());
+       }
+
+       public void updateTitleBar() {
+               if (currentPage.getTitle() != null)
+                       titleBar.setText(currentPage.getTitle());
+       }
+
+       public void updateWindowTitle() {
+               setTitle(guidedForm.getFormTitle());
+       }
+
+       protected boolean onCancel() {
+               return guidedForm.performCancel();
+       }
+
+       protected void nextPressed() {
+               GuidedForm.Page page = guidedForm.getNextPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void backPressed() {
+               GuidedForm.Page page = guidedForm.getPreviousPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void finishPressed() {
+               if (guidedForm.performFinish())
+                       closeShell(OK);
+       }
+
+       private static void setSwitchingFormData(Composite composite) {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               composite.setLayoutData(fdLabel);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java
new file mode 100644 (file)
index 0000000..f082796
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.ux.widgets.AbstractGuidedFormPage;
+import org.eclipse.swt.widgets.Composite;
+
+public abstract class SwtGuidedFormPage extends AbstractGuidedFormPage {
+
+       public SwtGuidedFormPage(String pageName) {
+               super(pageName);
+       }
+
+       public abstract void createControl(Composite parent);
+}
index 5fe22ae33b0aa42da989676390e714f288a8ac08..81326f345445b192f559912d8e1f8acc0ed0bbee 100644 (file)
@@ -36,7 +36,7 @@ public abstract class AbstractRapE4App implements ApplicationConfiguration {
 
                        @Override
                        public void handleException(Throwable throwable) {
-                               CmsFeedback.show("Unexpected RWT exception", throwable);
+                               CmsFeedback.error("Unexpected RWT exception", throwable);
                        }
                });
 
index 5947a2927e58ba3b27bbb79ad58f5294e0182156..7d4cd83315721d3227e801ddc5ea5bb280b00898 100644 (file)
@@ -146,7 +146,7 @@ public class CmsLoginLifecycle implements CmsView {
        public void exception(Throwable e) {
                String msg = "Unexpected exception in Eclipse 4 RAP";
                log.error(msg, e);
-               CmsFeedback.show(msg, e);
+               CmsFeedback.error(msg, e);
        }
 
        @Override
index b61259b7fa80d2939a827aba735c06b6d27938dc..bb6a2e1035fabcb176e583e584213442503059aa 100644 (file)
@@ -167,10 +167,10 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint,
                                return;
                }
                display.syncExec(() -> {
-                       CmsFeedback.show("Unexpected exception in CMS", e);
-                       exception = e;
-//                     log.error("Unexpected exception in CMS", e);
-                       doRefresh();
+                       // TODO internationalise
+                       CmsFeedback.error("Unexpected exception", e);
+                       // TODO report
+//                     doRefresh();
                });
        }
 
@@ -179,12 +179,12 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint,
                        Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
                                @Override
                                public Void run() {
-                                       if (exception != null) {
-                                               // TODO internationalise
-                                               CmsFeedback.show("Unexpected exception", exception);
-                                               exception = null;
-                                               // TODO report
-                                       }
+//                                     if (exception != null) {
+//                                             // TODO internationalise
+//                                             CmsFeedback.error("Unexpected exception", exception);
+//                                             exception = null;
+//                                             // TODO report
+//                                     }
                                        cmsWebApp.getCmsApp().refreshUi(ui, state);
                                        return null;
                                }
@@ -200,7 +200,7 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint,
 
        @Override
        public void navigateTo(String state) {
-               exception = null;
+//             exception = null;
                String title = setState(state);
                if (title != null)
                        doRefresh();