From 9e220ea5f945806f9fd8ef9f60ac7cf7b4b1edd1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 24 Dec 2020 11:24:34 +0100 Subject: [PATCH] Improve CMS dialogs and localisation. --- .../org/argeo/cms/web/CmsWebEntryPoint.java | 11 +- org.argeo.cms.ui/bnd.bnd | 1 - .../cms/ui/dialogs/ChangePasswordDialog.java | 79 ++++++ .../org/argeo/cms/ui/dialogs/CmsFeedback.java | 1 - .../cms/ui/dialogs/CmsMessageDialog.java | 16 +- .../argeo/cms/ui/dialogs/CmsWizardDialog.java | 1 - .../cms/ui/dialogs/LightweightDialog.java | 256 ++++++++++++++++++ org.argeo.cms/OSGI-INF/l10n/bundle.properties | 1 + .../OSGI-INF/l10n/bundle_fr.properties | 1 + org.argeo.cms/src/org/argeo/cms/CmsMsg.java | 2 +- .../src/org/argeo/cms/LocaleUtils.java | 24 +- .../src/org/argeo/cms/Localized.java | 1 + .../eclipse/ui/dialogs/LightweightDialog.java | 1 + 13 files changed, 380 insertions(+), 15 deletions(-) create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 9a023d6bc..4f2521fd4 100644 --- a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -4,6 +4,7 @@ import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicatio import java.security.PrivilegedAction; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -14,6 +15,7 @@ import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeConstants; +import org.argeo.cms.LocaleUtils; import org.argeo.cms.auth.CmsSession; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.HttpRequestCallbackHandler; @@ -104,8 +106,13 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL uxContext = new SimpleUxContext(); imageManager = new DefaultImageManager(); CmsSession cmsSession = getCmsSession(); - if (cmsSession != null) - RWT.setLocale(cmsSession.getLocale()); + if (cmsSession != null) { + UiContext.setLocale(cmsSession.getLocale()); + LocaleUtils.setThreadLocale(cmsSession.getLocale()); + } else { + Locale rwtLocale = RWT.getUISession().getLocale(); + LocaleUtils.setThreadLocale(rwtLocale); + } ui = cmsWebApp.getCmsApp().initUi(parent); ui.setData(CmsApp.UI_NAME_PROPERTY, uiName); ui.setLayoutData(CmsUiUtils.fillAll()); diff --git a/org.argeo.cms.ui/bnd.bnd b/org.argeo.cms.ui/bnd.bnd index 50ebd7ceb..ed842413b 100644 --- a/org.argeo.cms.ui/bnd.bnd +++ b/org.argeo.cms.ui/bnd.bnd @@ -5,7 +5,6 @@ Import-Package: org.eclipse.swt,\ org.eclipse.jface.window,\ org.eclipse.core.commands,\ javax.jcr.security,\ -org.argeo.eclipse.ui.dialogs,\ org.eclipse.rap.fileupload;version="[2.1,4)",\ org.eclipse.rap.rwt;version="[2.1,4)",\ org.eclipse.rap.rwt.application;version="[2.1,4)",\ diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java new file mode 100644 index 000000000..984f283df --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java @@ -0,0 +1,79 @@ +package org.argeo.cms.ui.dialogs; + +import java.security.PrivilegedAction; +import java.util.Arrays; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.CmsUserManager; +import org.argeo.cms.ui.CmsView; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** Dialog to change a password. */ +public class ChangePasswordDialog extends CmsMessageDialog { + private CmsUserManager cmsUserManager; + private CmsView cmsView; + + private PrivilegedAction doIt; + + public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { + super(parentShell, message, kind); + this.cmsUserManager = cmsUserManager; + cmsView = CmsView.getCmsView(parentShell); + } + + @Override + protected Control createInputArea(Composite userSection) { + addFormLabel(userSection, CmsMsg.currentPassword.lead()); + Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + previousPassword.setLayoutData(CmsUiUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.newPassword.lead()); + Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + newPassword.setLayoutData(CmsUiUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.repeatNewPassword.lead()); + Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + confirmPassword.setLayoutData(CmsUiUtils.fillWidth()); + + doIt = () -> { + if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { + try { + cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); + return OK; + } catch (Exception e1) { + cancel(); + CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); + return CANCEL; + } + } else { + cancel(); + CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); + return CANCEL; + } + }; + + pack(); + return previousPassword; + } + + @Override + protected void okPressed() { + Integer returnCode = cmsView.doAs(doIt); + if (returnCode.equals(OK)) { + super.okPressed(); + CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); + } + } + + private static Label addFormLabel(Composite parent, String label) { + Label lbl = new Label(parent, SWT.WRAP); + lbl.setText(label); +// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel); + return lbl; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java index 91dc7fb76..30e2ad34f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java @@ -7,7 +7,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsMsg; import org.argeo.eclipse.ui.Selected; -import org.argeo.eclipse.ui.dialogs.LightweightDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java index e076a1108..eb881c6bd 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java @@ -4,7 +4,6 @@ import org.argeo.cms.CmsMsg; import org.argeo.cms.ui.util.CmsUiUtils; import org.argeo.eclipse.ui.EclipseUiUtils; import org.argeo.eclipse.ui.Selected; -import org.argeo.eclipse.ui.dialogs.LightweightDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; @@ -60,12 +59,13 @@ public class CmsMessageDialog extends LightweightDialog { body.setLayout(bodyGridLayout); body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Label messageLbl = new Label(body, SWT.WRAP); - CmsUiUtils.markup(messageLbl); - messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - if (message != null) + if (message != null) { + Label messageLbl = new Label(body, SWT.WRAP); + CmsUiUtils.markup(messageLbl); + messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); messageLbl.setText(message); + } // buttons Composite buttons = new Composite(parent, SWT.NONE); @@ -131,6 +131,10 @@ public class CmsMessageDialog extends LightweightDialog { closeShell(CANCEL); } + protected void cancel() { + closeShell(CANCEL); + } + protected Point getInitialSize() { return new Point(400, 200); } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java index 93125be23..e4fe35d34 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java @@ -6,7 +6,6 @@ import org.argeo.cms.CmsMsg; import org.argeo.cms.ui.util.CmsUiUtils; import org.argeo.eclipse.ui.EclipseUiUtils; import org.argeo.eclipse.ui.Selected; -import org.argeo.eclipse.ui.dialogs.LightweightDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.wizard.IWizard; import org.eclipse.jface.wizard.IWizardContainer2; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java new file mode 100644 index 000000000..8f6347913 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java @@ -0,0 +1,256 @@ +package org.argeo.cms.ui.dialogs; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Generic lightweight dialog, not based on JFace. */ +public class LightweightDialog { + private final static Log log = LogFactory.getLog(LightweightDialog.class); + + // must be the same value as org.eclipse.jface.window.Window#OK + public final static int OK = 0; + // must be the same value as org.eclipse.jface.window.Window#CANCEL + public final static int CANCEL = 1; + + private Shell parentShell; + private Shell backgroundShell; + private Shell foregoundShell; + + private Integer returnCode = null; + private boolean block = true; + + private String title; + + /** Tries to find a display */ + private static Display getDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public LightweightDialog(Shell parentShell) { + this.parentShell = parentShell; + } + + public int open() { + if (foregoundShell != null) + throw new EclipseUiException("There is already a shell"); + backgroundShell = new Shell(parentShell, SWT.ON_TOP); + backgroundShell.setFullScreen(true); + // if (parentShell != null) { + // backgroundShell.setBounds(parentShell.getBounds()); + // } else + // backgroundShell.setMaximized(true); + backgroundShell.setAlpha(128); + backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); + foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); + if (title != null) + setTitle(title); + foregoundShell.setLayout(new GridLayout()); + foregoundShell.setSize(getInitialSize()); + createDialogArea(foregoundShell); + // shell.pack(); + // shell.layout(); + + Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP + Point dialogSize = foregoundShell.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + foregoundShell.setLocation(x, y); + + foregoundShell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = -2701270481953688763L; + + @Override + public void shellDeactivated(ShellEvent e) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + + @Override + public void shellClosed(ShellEvent e) { + notifyClose(); + } + + }); + + backgroundShell.open(); + foregoundShell.open(); + // after the foreground shell has been opened + backgroundShell.addFocusListener(new FocusListener() { + private static final long serialVersionUID = 3137408447474661070L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + }); + + if (block) { + block(); + } + if (returnCode == null) + returnCode = OK; + return returnCode; + } + + public void block() { + try { + runEventLoop(foregoundShell); + } catch (ThreadDeath t) { + returnCode = CANCEL; + if (log.isTraceEnabled()) + log.error("Thread death, canceling dialog", t); + } catch (Throwable t) { + returnCode = CANCEL; + log.error("Cannot open blocking lightweight dialog", t); + } + } + + private boolean hasChildShells() { + if (foregoundShell == null) + return false; + return foregoundShell.getShells().length != 0; + } + + // public synchronized int openAndWait() { + // open(); + // while (returnCode == null) + // try { + // wait(100); + // } catch (InterruptedException e) { + // // silent + // } + // return returnCode; + // } + + private synchronized void notifyClose() { + if (returnCode == null) + returnCode = CANCEL; + notifyAll(); + } + + protected void closeShell(int returnCode) { + this.returnCode = returnCode; + if (CANCEL == returnCode) + onCancel(); + if (foregoundShell != null && !foregoundShell.isDisposed()) { + foregoundShell.close(); + foregoundShell.dispose(); + foregoundShell = null; + } + + if (backgroundShell != null && !backgroundShell.isDisposed()) { + backgroundShell.close(); + backgroundShell.dispose(); + } + } + + protected Point getInitialSize() { + // if (exception != null) + // return new Point(800, 600); + // else + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = new Composite(parent, SWT.NONE); + dialogarea.setLayout(new GridLayout()); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return dialogarea; + } + + protected Shell getBackgroundShell() { + return backgroundShell; + } + + protected Shell getForegoundShell() { + return foregoundShell; + } + + public void setBlockOnOpen(boolean shouldBlock) { + block = shouldBlock; + } + + public void pack() { + foregoundShell.pack(); + } + + private void runEventLoop(Shell loopShell) { + Display display; + if (foregoundShell == null) { + display = Display.getCurrent(); + } else { + display = loopShell.getDisplay(); + } + + while (loopShell != null && !loopShell.isDisposed()) { + try { + if (!display.readAndDispatch()) { + display.sleep(); + } + } catch (UnsupportedOperationException e) { + throw e; + } catch (Throwable e) { + handleException(e); + } + } + if (!display.isDisposed()) + display.update(); + } + + protected void handleException(Throwable t) { + if (t instanceof ThreadDeath) { + // Don't catch ThreadDeath as this is a normal occurrence when + // the thread dies + throw (ThreadDeath) t; + } + // Try to keep running. + t.printStackTrace(); + } + + /** @return false, if the dialog should not be closed. */ + protected boolean onCancel() { + return true; + } + + public void setTitle(String title) { + this.title = title; + if (title != null && getForegoundShell() != null) + getForegoundShell().setText(title); + } + + public Integer getReturnCode() { + return returnCode; + } + +} \ No newline at end of file diff --git a/org.argeo.cms/OSGI-INF/l10n/bundle.properties b/org.argeo.cms/OSGI-INF/l10n/bundle.properties index 20d5b10e7..2e4944be7 100644 --- a/org.argeo.cms/OSGI-INF/l10n/bundle.properties +++ b/org.argeo.cms/OSGI-INF/l10n/bundle.properties @@ -9,6 +9,7 @@ currentPassword=Current password newPassword=New password repeatNewPassword=Repeat new password passwordChanged=Password changed +invalidPassword=Invalid password close=Close cancel=Cancel diff --git a/org.argeo.cms/OSGI-INF/l10n/bundle_fr.properties b/org.argeo.cms/OSGI-INF/l10n/bundle_fr.properties index 085ea7444..638c92793 100644 --- a/org.argeo.cms/OSGI-INF/l10n/bundle_fr.properties +++ b/org.argeo.cms/OSGI-INF/l10n/bundle_fr.properties @@ -9,6 +9,7 @@ currentPassword=mot de passe actuel newPassword=nouveau mot de passe repeatNewPassword=répéter le nouveau mot de passe passwordChanged=mot de passe changé +invalidPassword=mot de passe invalide close=Fermer cancel=Annuler diff --git a/org.argeo.cms/src/org/argeo/cms/CmsMsg.java b/org.argeo.cms/src/org/argeo/cms/CmsMsg.java index a69b419c5..7ae73961d 100644 --- a/org.argeo.cms/src/org/argeo/cms/CmsMsg.java +++ b/org.argeo.cms/src/org/argeo/cms/CmsMsg.java @@ -3,7 +3,7 @@ package org.argeo.cms; public enum CmsMsg implements Localized { username, password, login, logout, register, // password - changePassword, currentPassword, newPassword, repeatNewPassword, passwordChanged, + changePassword, currentPassword, newPassword, repeatNewPassword, passwordChanged, invalidPassword, // dialog close, cancel, ok, // wizard diff --git a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java index 24ca3558c..46b70d756 100644 --- a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java @@ -8,10 +8,20 @@ import java.util.ResourceBundle; import javax.security.auth.Subject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.cms.auth.CurrentUser; /** Utilities simplifying the development of localization enums. */ public class LocaleUtils { + private final static Log log = LogFactory.getLog(LocaleUtils.class); + + private final static ThreadLocal threadLocale = new ThreadLocal<>(); + + public static void setThreadLocale(Locale locale) { + threadLocale.set(locale); + } + public static String local(Enum en) { return local(en, getCurrentLocale(), "/OSGI-INF/l10n/bundle"); } @@ -64,10 +74,18 @@ public class LocaleUtils { } static Locale getCurrentLocale() { + Locale currentLocale = null; if (Subject.getSubject(AccessController.getContext()) != null) - return CurrentUser.locale(); - else - return Locale.getDefault(); + currentLocale = CurrentUser.locale(); + else if (threadLocale.get() != null) { + currentLocale = threadLocale.get(); + } + if (log.isDebugEnabled()) + log.debug("Thread #" + Thread.currentThread().getId() + " " + Thread.currentThread().getName() + " locale: " + + currentLocale); + if (currentLocale == null) + throw new IllegalStateException("No locale found"); + return currentLocale; // return UiContext.getLocale(); // FIXME look into Subject or settings // return Locale.getDefault(); diff --git a/org.argeo.cms/src/org/argeo/cms/Localized.java b/org.argeo.cms/src/org/argeo/cms/Localized.java index bc37608ed..d4558cac6 100644 --- a/org.argeo.cms/src/org/argeo/cms/Localized.java +++ b/org.argeo.cms/src/org/argeo/cms/Localized.java @@ -5,6 +5,7 @@ import java.util.Locale; /** Localized object. */ public interface Localized { + /** Default assumes that this is an {@link Enum} */ default Object local(Locale locale) { return LocaleUtils.local((Enum) this, locale); diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java index 606030e38..d00365bf2 100644 --- a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java +++ b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java @@ -18,6 +18,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** Generic lightweight dialog, not based on JFace. */ +@Deprecated public class LightweightDialog { private final static Log log = LogFactory.getLog(LightweightDialog.class); -- 2.30.2