From 632b55e1fdf15316fd5a460e98b547aa1d49d66f Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 21 Jan 2021 14:52:51 +0100 Subject: [PATCH] Introduce context overlay. --- .../cms/ui/forms/EditablePropertyString.java | 2 +- .../src/org/argeo/cms/ui/util/CmsUiUtils.java | 3 + .../argeo/cms/ui/widgets/ContextOverlay.java | 113 ++++++++++++++++++ .../argeo/cms/ui/widgets/EditableText.java | 31 ++++- .../argeo/cms/ui/widgets/StyledControl.java | 21 ++-- .../argeo/eclipse/ui/MouseDoubleClick.java | 26 ++++ 6 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java create mode 100644 org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java index 52bb596ee..c170e8c2f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java @@ -29,7 +29,7 @@ public class EditablePropertyString extends EditableText implements EditablePart public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message) throws RepositoryException { super(parent, style, node, true); - + //setUseTextAsLabel(true); this.propertyName = propertyName; this.message = message; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java index 6b411cc30..6db262963 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java @@ -156,6 +156,9 @@ public class CmsUiUtils implements CmsConstants { return new GridData(horizontalAlignment, horizontalAlignment, false, true); } + /* + * ROW LAYOUT + */ public static RowData rowData16px() { return new RowData(16, 16); } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java new file mode 100644 index 000000000..f8ca531c3 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java @@ -0,0 +1,113 @@ +package org.argeo.cms.ui.widgets; + +import org.argeo.cms.ui.util.CmsUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +/** + * Manages a lightweight shell which is related to a {@link Control}, typically + * in order to reproduce a dropdown semantic, but with more flexibility. + */ +public class ContextOverlay extends ScrolledPage { + private static final long serialVersionUID = 6702077429573324009L; + +// private Shell shell; + private Control control; + + private int maxHeight = 400; + + public ContextOverlay(Control control, int style) { + super(createShell(control, style), SWT.NONE); + Shell shell = getShell(); + setLayoutData(CmsUiUtils.fillAll()); + // TODO make autohide configurable? + //shell.addShellListener(new AutoHideShellListener()); + this.control = control; + control.addDisposeListener((e) -> { + dispose(); + shell.dispose(); + }); + } + + private static Composite createShell(Control control, int style) { + if (control == null) + throw new IllegalArgumentException("Control cannot be null"); + if (control.isDisposed()) + throw new IllegalArgumentException("Control is disposed"); + Shell shell = new Shell(control.getShell(), SWT.NO_TRIM); + shell.setLayout(CmsUiUtils.noSpaceGridLayout()); + Composite placeholder = new Composite(shell, SWT.BORDER); + placeholder.setLayoutData(CmsUiUtils.fillAll()); + placeholder.setLayout(CmsUiUtils.noSpaceGridLayout()); + return placeholder; + } + + public void show() { + Point relativeControlLocation = control.getLocation(); + Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y); + + int controlWidth = control.getBounds().width; + + Shell shell = getShell(); + + layout(true, true); + shell.pack(); + shell.layout(true, true); + int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x; + if (shell.getSize().y > maxHeight) { + shell.setSize(targetShellWidth, maxHeight); + } else { + shell.setSize(targetShellWidth, shell.getSize().y); + } + + int shellHeight = shell.getSize().y; + int controlHeight = control.getBounds().height; + Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight); + int displayHeight = shell.getDisplay().getBounds().height; + if (shellLocation.y + shellHeight > displayHeight) {// bottom of page + shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight); + } + shell.setLocation(shellLocation); + + if (getChildren().length != 0) + shell.open(); + if (!control.isDisposed()) + control.setFocus(); + } + + public void hide() { + getShell().setVisible(false); + onHide(); + } + + public boolean isShellVisible() { + if (isDisposed()) + return false; + return getShell().isVisible(); + } + + /** to be overridden */ + protected void onHide() { + // does nothing by default. + } + + private class AutoHideShellListener extends ShellAdapter { + private static final long serialVersionUID = 7743287433907938099L; + + @Override + public void shellDeactivated(ShellEvent e) { + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + // silent + } + if (!control.isDisposed() && !control.isFocusControl()) + hide(); + } + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java index 5fadbc07b..27b7c9b10 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java @@ -21,6 +21,8 @@ public class EditableText extends StyledControl { private Color highlightColor; private Composite highlight; + private boolean useTextAsLabel = false; + public EditableText(Composite parent, int style) { super(parent, style); editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); @@ -42,8 +44,11 @@ public class EditableText extends StyledControl { if (isEditing() && getEditable()) { return createText(box, style, true); } else { -// return createText(box, style, false); - return createLabel(box, style); + if (useTextAsLabel) { + return createTextLabel(box, style); + } else { + return createLabel(box, style); + } } } @@ -58,6 +63,18 @@ public class EditableText extends StyledControl { return lbl; } + protected Text createTextLabel(Composite box, String style) { + Text lbl = new Text(box, getStyle() | SWT.MULTI); + lbl.setEditable(false); + lbl.setLayoutData(CmsUiUtils.fillWidth()); + if (style != null) + CmsUiUtils.style(lbl, style); + CmsUiUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + protected Text createText(Composite box, String style, boolean editable) { highlight = new Composite(box, SWT.NONE); highlight.setBackground(highlightColor); @@ -111,8 +128,18 @@ public class EditableText extends StyledControl { throw new IllegalStateException("Unsupported control " + child.getClass()); } + /** @deprecated Use {@link #isEditable()} instead. */ + @Deprecated public boolean getEditable() { + return isEditable(); + } + + public boolean isEditable() { return editable; } + public void setUseTextAsLabel(boolean useTextAsLabel) { + this.useTextAsLabel = useTextAsLabel; + } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java index 1814131f4..5dde1f637 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java @@ -47,10 +47,10 @@ public abstract class StyledControl extends JcrComposite implements CmsConstants } protected Composite createContainer() { - Composite box = new Composite(this, SWT.INHERIT_DEFAULT); - setContainerLayoutData(box); - box.setLayout(CmsUiUtils.noSpaceGridLayout()); - return box; + Composite container = new Composite(this, SWT.INHERIT_DEFAULT); + setContainerLayoutData(container); + container.setLayout(CmsUiUtils.noSpaceGridLayout()); + return container; } public Control getControl() { @@ -67,8 +67,7 @@ public abstract class StyledControl extends JcrComposite implements CmsConstants // int height = control.getSize().y; String style = (String) EclipseUiSpecificUtils.getStyleData(control); clear(false); - control = createControl(box, style); - setControlLayoutData(control); + refreshControl(style); // add the focus listener to the newly created edition control if (focusListener != null) @@ -80,8 +79,13 @@ public abstract class StyledControl extends JcrComposite implements CmsConstants editing = false; String style = (String) EclipseUiSpecificUtils.getStyleData(control); clear(false); + refreshControl(style); + } + + protected void refreshControl(String style) { control = createControl(box, style); setControlLayoutData(control); + getParent().layout(true, true); } public void setStyle(String style) { @@ -91,11 +95,8 @@ public abstract class StyledControl extends JcrComposite implements CmsConstants if (currentStyle != null && currentStyle.equals(style)) return; - // Integer preferredHeight = control != null ? control.getSize().y : - // null; clear(true); - control = createControl(box, style); - setControlLayoutData(control); + refreshControl(style); if (style != null) { CmsUiUtils.style(box, style + "_box"); diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java new file mode 100644 index 000000000..49d11e5b8 --- /dev/null +++ b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java @@ -0,0 +1,26 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + +/** + * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface + * in order to use as a short lambda expression in UI code. + * {@link MouseListener#mouseDownouseEvent)} and + * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. + */ +@FunctionalInterface +public interface MouseDoubleClick extends MouseListener { + @Override + void mouseDoubleClick(MouseEvent e); + + @Override + default void mouseDown(MouseEvent e) { + // does nothing + } + + @Override + default void mouseUp(MouseEvent e) { + // does nothing + } +} -- 2.30.2