diff options
3 files changed, 276 insertions, 16 deletions
diff --git a/plugins/infra/properties/org.eclipse.papyrus.infra.properties.ui/src/org/eclipse/papyrus/infra/properties/ui/widgets/StringEditor.java b/plugins/infra/properties/org.eclipse.papyrus.infra.properties.ui/src/org/eclipse/papyrus/infra/properties/ui/widgets/StringEditor.java index a7425aef8e0..5810bdb4b0d 100644 --- a/plugins/infra/properties/org.eclipse.papyrus.infra.properties.ui/src/org/eclipse/papyrus/infra/properties/ui/widgets/StringEditor.java +++ b/plugins/infra/properties/org.eclipse.papyrus.infra.properties.ui/src/org/eclipse/papyrus/infra/properties/ui/widgets/StringEditor.java @@ -13,6 +13,7 @@ *****************************************************************************/ package org.eclipse.papyrus.infra.properties.ui.widgets; +import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; /** @@ -33,6 +34,18 @@ public class StringEditor extends AbstractPropertyEditor { * The style for the widget */ public StringEditor(Composite parent, int style) { - super(new org.eclipse.papyrus.infra.widgets.editors.StringEditor(parent, style)); + super(new org.eclipse.papyrus.infra.widgets.editors.StringEditor(parent, style | SWT.SEARCH | SWT.ICON_CANCEL)); + } + + /** + * @see org.eclipse.papyrus.infra.properties.ui.widgets.AbstractPropertyEditor#doBinding() + * + */ + @Override + protected void doBinding() { + super.doBinding(); + if (input != null && propertyPath != null && !input.isMandatory(propertyPath)) { + ((org.eclipse.papyrus.infra.widgets.editors.StringEditor) getEditor()).setOptional(true); + } } } diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/databinding/TextObservableValue.java b/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/databinding/TextObservableValue.java index 0ec2e903c7e..9357969ba93 100644 --- a/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/databinding/TextObservableValue.java +++ b/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/databinding/TextObservableValue.java @@ -11,7 +11,7 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Calin Glitia (Esterel Technologies SAS) - Bug 497496 - * + * *****************************************************************************/ package org.eclipse.papyrus.infra.widgets.databinding; @@ -20,6 +20,7 @@ import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.ValueDiff; import org.eclipse.papyrus.infra.tools.databinding.AggregatedObservable; import org.eclipse.papyrus.infra.widgets.providers.UnchangedObject; +import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; @@ -32,6 +33,11 @@ import org.eclipse.swt.widgets.Text; */ public class TextObservableValue extends AbstractObservableValue implements Listener { + // Flag to avoid firing a change event when focus comes in and out, + // without any user change occurring in the text field + private boolean wasChanged = false; + private boolean isReset = false; + private Text text; private int eventType; @@ -64,6 +70,16 @@ public class TextObservableValue extends AbstractObservableValue implements List this.modelProperty = (AggregatedObservable) modelProperty; } this.text.addListener(eventType, this); + this.text.addModifyListener(event -> { + this.wasChanged = true; + this.isReset = false; + }); + + this.text.addListener(SWT.DefaultSelection, event -> { + if (event.detail == SWT.ICON_CANCEL) { + clear(); + } + }); } @Override @@ -77,10 +93,19 @@ public class TextObservableValue extends AbstractObservableValue implements List return null; } - if (UnchangedObject.instance.toString().equals(text.getText())) { - return null; + if (wasChanged) { + // XXX We don't support special values, so we have to rely on null in two + // distinct cases. In case of single-selection, 'null' means 'null' (Unset or set(null)) + // In case of multi-selection, 'null' means 'unchanged' + if (isReset) { + return null; + } else if (UnchangedObject.instance.toString().equals(text.getText())) { + return null; + } else { + return text.getText(); + } } else { - return text.getText(); + return currentValue; } } @@ -91,14 +116,14 @@ public class TextObservableValue extends AbstractObservableValue implements List } if (modelProperty != null && modelProperty.hasDifferentValues()) { this.text.setText(UnchangedObject.instance.toString()); - this.currentValue = UnchangedObject.instance; + storeValue(UnchangedObject.instance); } else { if (value instanceof String) { this.text.setText((String) value); - this.currentValue = value; + storeValue(value); } else if (value == null) { this.text.setText(""); //$NON-NLS-1$ - this.currentValue = null; + storeValue(null); } } } @@ -111,10 +136,10 @@ public class TextObservableValue extends AbstractObservableValue implements List final Object oldValue = currentValue; final Object newValue = getValue(); - if (newValue == null) { + if (newValue == null && !isReset) { return; } - currentValue = newValue; + storeValue(newValue); if ((eventType & event.type) != 0) { fireValueChange(new ValueDiff() { @@ -133,4 +158,16 @@ public class TextObservableValue extends AbstractObservableValue implements List } } + public void clear() { + this.text.setText(""); + this.wasChanged = true; + this.isReset = true; + } + + private void storeValue(Object value) { + this.currentValue = value; + this.wasChanged = false; + this.isReset = false; + } + } diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/editors/StringEditor.java b/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/editors/StringEditor.java index 6d513afb03c..84a227797ce 100644 --- a/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/editors/StringEditor.java +++ b/plugins/infra/ui/org.eclipse.papyrus.infra.widgets/src/org/eclipse/papyrus/infra/widgets/editors/StringEditor.java @@ -22,17 +22,38 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.papyrus.infra.widgets.databinding.TextObservableValue; import org.eclipse.papyrus.infra.widgets.selectors.StringSelector; import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleControlAdapter; +import org.eclipse.swt.accessibility.AccessibleControlEvent; +import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.plugin.AbstractUIPlugin; /** * A Property Editor representing a single-line or multi-line String value as a @@ -46,11 +67,33 @@ import org.eclipse.swt.widgets.Text; */ public class StringEditor extends AbstractValueEditor implements KeyListener, ModifyListener { + + private static final String CLEAR_ICON = "org.eclipse.ui.internal.dialogs.CLEAR_ICON"; //$NON-NLS-1$ + private static final String DISABLED_CLEAR_ICON = "org.eclipse.ui.internal.dialogs.DCLEAR_ICON"; //$NON-NLS-1$ + + static { + ImageDescriptor descriptor = AbstractUIPlugin + .imageDescriptorFromPlugin(PlatformUI.PLUGIN_ID, + "$nl$/icons/full/etool16/clear_co.png"); //$NON-NLS-1$ + if (descriptor != null) { + JFaceResources.getImageRegistry().put(CLEAR_ICON, descriptor); + } + descriptor = AbstractUIPlugin.imageDescriptorFromPlugin( + PlatformUI.PLUGIN_ID, "$nl$/icons/full/dtool16/clear_co.png"); //$NON-NLS-1$ + if (descriptor != null) { + JFaceResources.getImageRegistry().put(DISABLED_CLEAR_ICON, descriptor); + } + } + /** * The text box for editing this editor's value */ protected final Text text; + private boolean wasChanged = false; + + private String initialText; + private int delay = 600; private boolean validateOnDelay = false; @@ -61,6 +104,10 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo private TimerTask changeColorTask; + private boolean isOptional; + + private Label clearButton; + private final static int DEFAULT_HEIGHT_HINT = 55; private final static int DEFAULT_WIDTH_HINT = 100; @@ -141,13 +188,54 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo style = style | SWT.V_SCROLL; } - text = factory.createText(this, null, style); - text.setLayoutData(data); + int clearStyle = SWT.SEARCH | SWT.ICON_CANCEL; + + if ((style & clearStyle) == clearStyle) { + Text testText = factory.createText(this, null, style); + // Test if SEARCH is natively supported + if ((testText.getStyle() & clearStyle) != clearStyle) { + // Not natively supported (e.g. Windows); create a custom clear icon + testText.dispose(); + + final int borderStyle = factory.getBorderStyle(); + final Composite textWrapper = factory.createComposite(this, borderStyle); + textWrapper.setLayoutData(data); + + GridLayoutFactory.fillDefaults().numColumns(2).margins(0, 0).applyTo(textWrapper); + try { + // Remove border from the Text Control and add it to the wrapping composite + factory.setBorderStyle(SWT.NONE); + text = factory.createText(textWrapper, null, SWT.NONE); + } finally { + factory.setBorderStyle(borderStyle); + } + text.setLayoutData(GridDataFactory.copyData(data)); + + addClearIcon(text, textWrapper); + } else { + // Natively supported; just use it. + text = testText; + text.setLayoutData(data); + // Implement custom clear behavior + text.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + if (e.detail == SWT.CANCEL) { + clearText(); + e.doit = false; + } + } + }); + } + } else { + text = factory.createText(this, null, style); + text.setLayoutData(data); + } if (label != null) { super.label.setLayoutData(getLabelLayoutData()); - } + text.addKeyListener(this); text.addModifyListener(this); setCommitOnFocusLost(text); @@ -158,6 +246,108 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo } + // From org.eclipse.ui.dialogs.FilteredTree + private void addClearIcon(Text text, Composite parent) { + // only create the button if the text widget doesn't support one + // natively + if ((text.getStyle() & SWT.ICON_CANCEL) == 0) { + final Image inactiveImage = JFaceResources.getImageRegistry().getDescriptor(DISABLED_CLEAR_ICON).createImage(); + final Image activeImage = JFaceResources.getImageRegistry().getDescriptor(CLEAR_ICON).createImage(); + final Image pressedImage = new Image(getDisplay(), activeImage, SWT.IMAGE_GRAY); + + final Label clearButton = new Label(parent, SWT.NONE); + clearButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + clearButton.setImage(inactiveImage); + clearButton.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); + clearButton.setToolTipText("Clear"); + clearButton.addMouseListener(new MouseAdapter() { + private MouseMoveListener fMoveListener; + + @Override + public void mouseDown(MouseEvent e) { + clearButton.setImage(pressedImage); + fMoveListener = new MouseMoveListener() { + private boolean fMouseInButton = true; + + @Override + public void mouseMove(MouseEvent e) { + boolean mouseInButton = isMouseInButton(e); + if (mouseInButton != fMouseInButton) { + fMouseInButton = mouseInButton; + clearButton.setImage(mouseInButton ? pressedImage : inactiveImage); + } + } + }; + clearButton.addMouseMoveListener(fMoveListener); + } + + @Override + public void mouseUp(MouseEvent e) { + if (fMoveListener != null) { + clearButton.removeMouseMoveListener(fMoveListener); + fMoveListener = null; + boolean mouseInButton = isMouseInButton(e); + clearButton.setImage(mouseInButton ? activeImage : inactiveImage); + if (mouseInButton) { + clearText(); + text.setFocus(); + } + } + } + + private boolean isMouseInButton(MouseEvent e) { + Point buttonSize = clearButton.getSize(); + return 0 <= e.x && e.x < buttonSize.x && 0 <= e.y && e.y < buttonSize.y; + } + }); + clearButton.addMouseTrackListener(new MouseTrackListener() { + @Override + public void mouseEnter(MouseEvent e) { + clearButton.setImage(activeImage); + } + + @Override + public void mouseExit(MouseEvent e) { + clearButton.setImage(inactiveImage); + } + + @Override + public void mouseHover(MouseEvent e) { + } + }); + clearButton.addDisposeListener(e -> { + inactiveImage.dispose(); + activeImage.dispose(); + pressedImage.dispose(); + }); + clearButton.getAccessible().addAccessibleListener( + new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result = WorkbenchMessages.FilteredTree_AccessibleListenerClearButton; + } + }); + clearButton.getAccessible().addAccessibleControlListener( + new AccessibleControlAdapter() { + @Override + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_PUSHBUTTON; + } + }); + + this.clearButton = clearButton; + } + } + + protected void clearText() { + if (isOptional && widgetObservable instanceof TextObservableValue) { + ((TextObservableValue) widgetObservable).clear(); + } else { + text.setText(""); + } + notifyChange(); + } + @Override protected GridData getLabelLayoutData() { GridData result = super.getLabelLayoutData(); @@ -235,12 +425,19 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo */ @Override public Object getValue() { - return text.getText(); + // If the user never typed anything, return the raw (potentially null) original value + // This is to avoid changing from "null" to "" (empty string) when the user simply + // focuses the control in and out. + return wasChanged ? text.getText() : initialText; } @Override public void setReadOnly(boolean readOnly) { text.setEnabled(!readOnly); + if (clearButton != null) { + clearButton.setVisible(!readOnly); + setExclusion(clearButton, readOnly); + } } @Override @@ -269,9 +466,12 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo public void setValue(Object value) { if (value instanceof String) { this.text.setText((String) value); + initialText = (String) value; } else { - this.text.setText(""); //$NON-NLS-1$; + this.text.setText(""); //$NON-NLS-1$ ; + initialText = null; } + wasChanged = false; } /** @@ -353,6 +553,7 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo }; timer.schedule(currentValidateTask, delay); } + wasChanged = true; if (targetValidator != null) { IStatus status = targetValidator.validate(text.getText()); updateStatus(status); @@ -454,7 +655,7 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo text.setBackground(ERROR); text.update(); } else { - IStatus status = (IStatus) binding.getValidationStatus().getValue(); + IStatus status = binding.getValidationStatus().getValue(); switch (status.getSeverity()) { case IStatus.OK: case IStatus.WARNING: @@ -479,4 +680,13 @@ public class StringEditor extends AbstractValueEditor implements KeyListener, Mo } } + /** + * Indicate that this editor handles an optional value. If optional is true, + * clearing this editor (via the Clear button) will set a <code>null</code> value; + * otherwise it will set an empty string value ("") + */ + public void setOptional(boolean optional) { + this.isOptional = optional; + } + } |