From: Mathieu Baudier Date: Tue, 20 Sep 2022 06:39:05 +0000 (+0200) Subject: Introduce guided form to replace wizards X-Git-Tag: v2.3.10~35 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=257d82f6cab077fa0f58b2c4ad294ab4840155de Introduce guided form to replace wizards --- 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 index 000000000..f1e89f1f4 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java @@ -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 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 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 index 000000000..d56551008 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java @@ -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 index 000000000..de8554e09 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java @@ -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 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(); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java index ff752c8ca..ddb6e1b33 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java @@ -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; diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java index a01c919e9..ddb8a7f95 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java @@ -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 index 000000000..a7242b9a9 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java @@ -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 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 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 index 000000000..f082796c2 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java @@ -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); +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java index 5fe22ae33..81326f345 100644 --- a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java @@ -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); } }); diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java index 5947a2927..7d4cd8331 100644 --- a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java @@ -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 diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index b61259b7f..bb6a2e103 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -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() { @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();