Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Charles David2018-01-15 14:18:24 +0000
committerSt├ęphane B├ęgaudeau2018-01-26 14:33:35 +0000
commit0404d254e5e6fda22b6909957a5d6706ee24c270 (patch)
tree54d81a9a9ac5270a97ed25b9db3956aceb3f803b
parente65e13cd671d3ced23d717b5aaa9308f24ef2452 (diff)
downloadorg.eclipse.eef-0404d254e5e6fda22b6909957a5d6706ee24c270.tar.gz
org.eclipse.eef-0404d254e5e6fda22b6909957a5d6706ee24c270.tar.xz
org.eclipse.eef-0404d254e5e6fda22b6909957a5d6706ee24c270.zip
[528330] Handle possible text loss on widget locking
Change-Id: I2405126335a9b6a5578082ff4c4ee455fd97327e Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
-rw-r--r--plugins/org.eclipse.eef.ide.ui/plugin.properties2
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java6
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java153
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextMemento.java157
4 files changed, 234 insertions, 84 deletions
diff --git a/plugins/org.eclipse.eef.ide.ui/plugin.properties b/plugins/org.eclipse.eef.ide.ui/plugin.properties
index 94995f4e9..64c1e477d 100644
--- a/plugins/org.eclipse.eef.ide.ui/plugin.properties
+++ b/plugins/org.eclipse.eef.ide.ui/plugin.properties
@@ -43,3 +43,5 @@ EEFTextLifecycleManager_conflictDialog_title = Model Changed During Edition
EEFTextLifecycleManager_conflictDialog_message = The model has changed in an incompatible way while you were editing this widget.\n\nNew value from the model: {0}\nEdited value: {1}
EEFTextLifecycleManager_conflictDialog_choiceNewModelValue = Use new value from the model
EEFTextLifecycleManager_conflictDialog_choiceLocalEditedValue = Keep the edited value
+EEFTextLifecycleManager_textLossByLocking_title = Input Lost
+EEFTextLifecycleManager_textLossByLocking_message = The widget has been locked by an asynchronous operation.\nThe current input will be lost: {0}\nDo you want a copy to be placed in the clipboard?
diff --git a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java
index 9848d5208..682c67168 100644
--- a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java
+++ b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java
@@ -97,6 +97,12 @@ public final class Messages {
@TranslatableMessage
public static String EEFTextLifecycleManager_conflictDialog_choiceLocalEditedValue;
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_textLossByLocking_title;
+
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_textLossByLocking_message;
+
// CHECKSTYLE:ON
/**
diff --git a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java
index 4d9be6b66..8f59d5d89 100644
--- a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java
+++ b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java
@@ -12,6 +12,7 @@ package org.eclipse.eef.ide.ui.internal.widgets;
import java.text.MessageFormat;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.runtime.IStatus;
@@ -28,6 +29,7 @@ import org.eclipse.eef.core.api.EditingContextAdapter;
import org.eclipse.eef.core.api.controllers.EEFControllersFactory;
import org.eclipse.eef.core.api.controllers.IEEFTextController;
import org.eclipse.eef.core.api.controllers.IEEFWidgetController;
+import org.eclipse.eef.core.api.utils.EvalFactory;
import org.eclipse.eef.ide.ui.api.widgets.AbstractEEFWidgetLifecycleManager;
import org.eclipse.eef.ide.ui.api.widgets.EEFStyleHelper;
import org.eclipse.eef.ide.ui.api.widgets.EEFStyleHelper.IEEFTextStyleCallback;
@@ -35,13 +37,14 @@ import org.eclipse.eef.ide.ui.internal.EEFIdeUiPlugin;
import org.eclipse.eef.ide.ui.internal.Messages;
import org.eclipse.eef.ide.ui.internal.preferences.EEFPreferences;
import org.eclipse.eef.ide.ui.internal.widgets.styles.EEFColor;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.sirius.common.interpreter.api.IInterpreter;
import org.eclipse.sirius.common.interpreter.api.IVariableManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyListener;
@@ -51,7 +54,6 @@ import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.forms.widgets.FormToolkit;
/**
@@ -149,76 +151,6 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
*/
private boolean isDirty;
- // CHECKSTYLE:OFF
- /**
- * A simple data record to remember un-commited user input for recovery in case of concurrent changes that could
- * override this input.
- */
- private static class Memento {
- /**
- * The key used to attach the user input memento to the widget.
- */
- public static final String KEY = "eef.widget.text.memento"; //$NON-NLS-1$
-
- /**
- * The widget description that was current when the memento was created.
- */
- public final EEFTextDescription description;
- /**
- * The "self" target element that was current when the memento was created.
- */
- public final Object self;
- /**
- * The reference value corresponding to the pristine text computed from the model by the valueExpression.
- */
- public final String referenceValue;
- /**
- * The last (full) value of the text widget entered by the user but not commited yet.
- */
- public final String userInput;
-
- public Memento(EEFTextDescription description, Object self, String referenceValue, String userInpu) {
- this.description = description;
- this.self = self;
- this.referenceValue = referenceValue;
- this.userInput = userInpu;
- }
-
- public boolean appliesTo(EEFTextLifecycleManager lm) {
- return this.description == lm.description && this.self == lm.variableManager.getVariables().get(EEFExpressionUtils.SELF);
- }
-
- public void store(Widget w) {
- w.setData(KEY, this);
- }
-
- public static Memento of(Widget w) {
- Object data = w.getData(KEY);
- if (data instanceof Memento) {
- return (Memento) data;
- } else {
- return null;
- }
- }
-
- public static void remove(Widget w) {
- w.setData(KEY, null);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- String newLine = "\n"; //$NON-NLS-1$
- sb.append("Desc: " + EcoreUtil.getURI(description)).append(newLine); //$NON-NLS-1$
- sb.append("Self: " + EcoreUtil.getURI((EObject) self)).append(newLine); //$NON-NLS-1$
- sb.append("Reference Value: " + referenceValue).append(newLine); //$NON-NLS-1$
- sb.append("User Input: " + userInput).append(newLine); //$NON-NLS-1$
- sb.append(newLine);
- return sb.toString();
- }
- }
- // CHECKSTYLE:ON
-
/**
* The constructor.
*
@@ -320,7 +252,7 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
this.isDirty = true;
Object self = this.variableManager.getVariables().get(EEFExpressionUtils.SELF);
String userInput = ((StyledText) event.widget).getText();
- Memento memento = new Memento(this.description, self, this.referenceValue, userInput);
+ EEFTextMemento memento = new EEFTextMemento(this.description, self, this.referenceValue, userInput);
memento.store(event.widget);
}
};
@@ -370,20 +302,21 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
if (value != null) {
newDisplayText[0] = Util.firstNonNull(value.toString(), newDisplayText[0]);
}
- Memento m = Memento.of(text);
- if (m != null) {
- boolean resettingToPreviousReferenceValue = Objects.equals(newDisplayText[0], m.referenceValue);
- boolean userHasUncommitedInput = !Objects.equals(newDisplayText[0], m.userInput);
- if (m.appliesTo(this) && userHasUncommitedInput) {
+ EEFTextMemento memento = EEFTextMemento.of(text);
+ if (memento != null) {
+ boolean resettingToPreviousReferenceValue = Objects.equals(newDisplayText[0], memento.getReferenceValue());
+ boolean userHasUncommitedInput = !Objects.equals(newDisplayText[0], memento.getUserInput());
+ if (memento.appliesTo(this.description, this.variableManager.getVariables()) && userHasUncommitedInput) {
if (resettingToPreviousReferenceValue) {
// Custom user input overrides resetting the same previous referenceValue.
- newDisplayText[0] = m.userInput;
- } else if (!Objects.equals(m.userInput, newDisplayText[0])) {
+ newDisplayText[0] = memento.getUserInput();
+ } else if (!Objects.equals(memento.getUserInput(), newDisplayText[0])) {
// Conflict must be resolved somehow.
- newDisplayText[0] = resolveEditionConflict(this.text.getShell(), m.referenceValue, m.userInput, newDisplayText[0]);
+ newDisplayText[0] = resolveEditionConflict(this.text.getShell(), memento.getReferenceValue(), memento.getUserInput(),
+ newDisplayText[0]);
}
}
- Memento.remove(text);
+ EEFTextMemento.remove(text);
}
return newDisplayText[0];
}
@@ -481,7 +414,7 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
refresh();
}
this.isDirty = false;
- Memento.remove(this.text);
+ EEFTextMemento.remove(this.text);
this.setStyle();
} finally {
updateInProgress.set(false);
@@ -545,6 +478,21 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
protected void lockedByOther() {
this.lockedByOtherInProgress.set(true);
try {
+ /*
+ * Disabling the widget will prevent the user to recover any local version of the text widget. Detect this
+ * case and open a popup with the option to copy the local text to the clipboard.
+ */
+ String textFromModel = computeTextFromModel();
+ EEFTextMemento memento = EEFTextMemento.of(text);
+ if (memento != null) {
+ boolean userHasUncommitedInput = !Objects.equals(textFromModel, memento.getUserInput());
+ if (memento.appliesTo(this.description, this.variableManager.getVariables()) && userHasUncommitedInput) {
+ notifyTextLossOnLock(memento.getUserInput(), textFromModel);
+ // Update the displayed text to avoid confusion.
+ this.text.setText(textFromModel);
+ }
+ EEFTextMemento.remove(text);
+ }
super.lockedByOther();
} finally {
this.lockedByOtherInProgress.set(false);
@@ -552,6 +500,43 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
}
/**
+ * Notify the end user that his current input will be lost as the underlying widget (and model element) has been
+ * locked by a remote/async change.
+ *
+ * @param userInput
+ * the current text entered by the user.
+ * @param textFromModel
+ * the text that will replace the current input.
+ */
+ protected void notifyTextLossOnLock(String userInput, String textFromModel) {
+ Shell shell = this.text.getShell();
+ if (MessageDialog.openQuestion(shell, Messages.EEFTextLifecycleManager_textLossByLocking_title,
+ MessageFormat.format(Messages.EEFTextLifecycleManager_textLossByLocking_title, userInput))) {
+ Clipboard clipboard = new Clipboard(shell.getDisplay());
+ clipboard.setContents(new Object[] { userInput }, new Transfer[] { TextTransfer.getInstance() });
+ clipboard.dispose();
+ }
+ }
+
+ /**
+ * Compute the text the widget should display given the current state of the underlying model (independently of any
+ * potential user input in the widget).
+ *
+ * @return the text to display according to the model's current state.
+ */
+ private String computeTextFromModel() {
+ String valueExpression = this.description.getValueExpression();
+ Object result = EvalFactory.of(this.interpreter, this.variableManager).evaluate(valueExpression);
+
+ // @formatter:off
+ return Optional.ofNullable(result)
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .orElse(""); //$NON-NLS-1$
+ // @formatter:on
+ }
+
+ /**
* {@inheritDoc}
*
* @see org.eclipse.eef.ide.ui.api.widgets.AbstractEEFWidgetLifecycleManager#setEnabled(boolean)
diff --git a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextMemento.java b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextMemento.java
new file mode 100644
index 000000000..8f193145c
--- /dev/null
+++ b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextMemento.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2017, 2018 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.eef.ide.ui.internal.widgets;
+
+import java.util.Map;
+
+import org.eclipse.eef.EEFTextDescription;
+import org.eclipse.eef.core.api.EEFExpressionUtils;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.swt.widgets.Widget;
+
+/**
+ * A simple data record to remember un-commited user input for recovery in case of concurrent changes that could
+ * override this input.
+ */
+public class EEFTextMemento {
+ /**
+ * The key used to attach the user input memento to the widget.
+ */
+ public static final String KEY = "eef.widget.text.memento"; //$NON-NLS-1$
+
+ /**
+ * The widget description that was current when the memento was created.
+ */
+ private final EEFTextDescription description;
+
+ /**
+ * The "self" target element that was current when the memento was created.
+ */
+ private final Object self;
+
+ /**
+ * The reference value corresponding to the pristine text computed from the model by the valueExpression.
+ */
+ private final String referenceValue;
+
+ /**
+ * The last (full) value of the text widget entered by the user but not commited yet.
+ */
+ private final String userInput;
+
+ /**
+ * The constructor.
+ *
+ * @param description
+ * The description of the text widget.
+ * @param self
+ * The "self" target element
+ * @param referenceValue
+ * The reference value corresponding to the pristine text computed from the model
+ * @param userInput
+ * The user input
+ */
+ public EEFTextMemento(EEFTextDescription description, Object self, String referenceValue, String userInput) {
+ this.description = description;
+ this.self = self;
+ this.referenceValue = referenceValue;
+ this.userInput = userInput;
+ }
+
+ /**
+ * Indicates if the memento applies to the given lifecycle manager.
+ *
+ * @param textDescription
+ * The description of the text widget of the lifecycle manager
+ * @param variables
+ * The variables of the lifecycle manager
+ * @return <code>true</code> if the memento applies to the lifecycle manager, <code>false</code> otherwise.
+ */
+ public boolean appliesTo(EEFTextDescription textDescription, Map<String, Object> variables) {
+ return this.description == textDescription && this.self == variables.get(EEFExpressionUtils.SELF);
+ }
+
+ /**
+ * Stores the memento in the given widget.
+ *
+ * @param widget
+ * The widget
+ */
+ public void store(Widget widget) {
+ widget.setData(KEY, this);
+ }
+
+ /**
+ * Returns the memento of the given widget.
+ *
+ * @param widget
+ * The widget
+ * @return The memento of the given widget
+ */
+ public static EEFTextMemento of(Widget widget) {
+ Object data = widget.getData(KEY);
+ if (data instanceof EEFTextMemento) {
+ return (EEFTextMemento) data;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Removes the memento of the given widget.
+ *
+ * @param widget
+ * The widget
+ */
+ public static void remove(Widget widget) {
+ widget.setData(KEY, null);
+ }
+
+ /**
+ * Return the description.
+ *
+ * @return the description
+ */
+ public EEFTextDescription getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Return the referenceValue.
+ *
+ * @return the referenceValue
+ */
+ public String getReferenceValue() {
+ return this.referenceValue;
+ }
+
+ /**
+ * Return the userInput.
+ *
+ * @return the userInput
+ */
+ public String getUserInput() {
+ return this.userInput;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ String newLine = "\n"; //$NON-NLS-1$
+ sb.append("Desc: " + EcoreUtil.getURI(description)).append(newLine); //$NON-NLS-1$
+ sb.append("Self: " + EcoreUtil.getURI((EObject) self)).append(newLine); //$NON-NLS-1$
+ sb.append("Reference Value: " + referenceValue).append(newLine); //$NON-NLS-1$
+ sb.append("User Input: " + userInput).append(newLine); //$NON-NLS-1$
+ sb.append(newLine);
+ return sb.toString();
+ }
+} \ No newline at end of file

Back to the top