Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Charles David2017-12-07 13:07:24 +0000
committerPierre-Charles David2017-12-19 10:14:10 +0000
commit51a0df0bd2c22027f9005691b33fcac6b369d0e6 (patch)
tree9e4a2212b4674ad23dd57765e648030252889da8
parent14b7c04f0b08b1b012bef6bea99b24f01c3dea39 (diff)
downloadorg.eclipse.eef-51a0df0bd2c22027f9005691b33fcac6b369d0e6.tar.gz
org.eclipse.eef-51a0df0bd2c22027f9005691b33fcac6b369d0e6.tar.xz
org.eclipse.eef-51a0df0bd2c22027f9005691b33fcac6b369d0e6.zip
[528134] Make the reaction in case of conflict configurable
The implementation in EEFTextLifecycleManager uses a new preference TEXT_CONFLICT_RESOLUTION_MODE, which is not exposed in the UI but can be configured programmatically using EEFPreferences.setTextConflictResolutionMode(). The default preference value can also be overridden by configuration, using the -pluginCustomization startup flag to point to a plugin_customization.ini file with, for example: org.eclipse.eef.ide.ui/TEXT_CONFLICT_RESOLUTION_MODE=USE_LOCAL_VERSION EEFTextLifecycleManager supports three modes: * USE_MODEL_VERSION: overwrite the widget's content with the value computed from the new version of the model (no user feedback). This is the default. * USE_LOCAL_VERSION: keep the current value begin edited by the end-user in the widget (no user feedback); * ASK_USER: open a simple dialog box to ask the user which version to keep. Note that the simple dialog provided is only suitable for short (single-line) text fields. Conflict resolution can further be customized by providing a custom LifecycleManager which extends the default EEFTextLifecycleManager and overrides either: * askUserToResolveConflict() to take the preference in consideration, but implement ASK_USER differently, for example with a more sophisticated dialog. * resolveEditionConflict() to provide a completely different strategy (possibly ignoring the preference). Bug: 528134 Change-Id: I55527d2ceb31b691c4c3d5a63f72f21a6bc5643c Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
-rw-r--r--plugins/org.eclipse.eef.ide.ui/plugin.properties5
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/Messages.java12
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/preferences/EEFPreferences.java97
-rw-r--r--plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/widgets/EEFTextLifecycleManager.java161
4 files changed, 228 insertions, 47 deletions
diff --git a/plugins/org.eclipse.eef.ide.ui/plugin.properties b/plugins/org.eclipse.eef.ide.ui/plugin.properties
index 07bb99c2a..33000c5e8 100644
--- a/plugins/org.eclipse.eef.ide.ui/plugin.properties
+++ b/plugins/org.eclipse.eef.ide.ui/plugin.properties
@@ -37,3 +37,8 @@ AbstractEEFWidgetLifecycleManager_noDescriptionAvailable=No description availabl
AbstractEEFWidgetLifecycleManager_lockedByOther=This widget is locked by another user
AbstractEEFWidgetLifecycleManager_lockedByMe=This widget is locked for other users
AbstractEEFWidgetLifecycleManager_lockedNoWrite=This widget is locked because of a permission issue
+
+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
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 8a930b27a..5ef270304 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
@@ -82,6 +82,18 @@ public final class Messages {
@TranslatableMessage
public static String AbstractEEFWidgetLifecycleManager_lockedNoWrite;
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_conflictDialog_title;
+
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_conflictDialog_message;
+
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_conflictDialog_choiceNewModelValue;
+
+ @TranslatableMessage
+ public static String EEFTextLifecycleManager_conflictDialog_choiceLocalEditedValue;
+
// CHECKSTYLE:ON
/**
diff --git a/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/preferences/EEFPreferences.java b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/preferences/EEFPreferences.java
new file mode 100644
index 000000000..1e01dd0ce
--- /dev/null
+++ b/plugins/org.eclipse.eef.ide.ui/src/org/eclipse/eef/ide/ui/internal/preferences/EEFPreferences.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2017 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.preferences;
+
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.eef.ide.ui.internal.EEFIdeUiPlugin;
+import org.eclipse.eef.ide.ui.internal.widgets.EEFTextLifecycleManager.ConflictResolutionMode;
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * Preferences for the EEF UI.
+ *
+ * @author pcdavid
+ */
+public final class EEFPreferences {
+ /**
+ * The key for the text conflict resolution mode.
+ */
+ public static final String TEXT_CONFLICT_RESOLUTION_MODE = "TEXT_CONFLICT_RESOLUTION_MODE"; //$NON-NLS-1$
+
+ /**
+ * The default value for text conflict resolution mode.
+ */
+ public static final ConflictResolutionMode DEFAULT_TEXT_CONFLICT_RESOLUTION_MODE = ConflictResolutionMode.ASK_USER;
+
+ /**
+ * The EEF Common preference scope.
+ */
+ private static final IEclipsePreferences PREFERENCES_SCOPE = InstanceScope.INSTANCE.getNode(EEFIdeUiPlugin.PLUGIN_ID);
+
+ /**
+ * The EEF Common default preference scope.
+ */
+ private static final IEclipsePreferences DEFAULT_PREFERENCES_SCOPE = DefaultScope.INSTANCE.getNode(EEFIdeUiPlugin.PLUGIN_ID);
+
+ /**
+ * The constructor.
+ */
+ private EEFPreferences() {
+ // prevent instantiation
+ }
+
+ /**
+ * Indicates how text conflicts should be resolved.
+ *
+ * @return the conflict resolution mode to be used.
+ */
+ public static ConflictResolutionMode getTextConflictResolutionMode() {
+ String mode = PREFERENCES_SCOPE.get(TEXT_CONFLICT_RESOLUTION_MODE, null);
+ if (mode == null) {
+ // No explicit preference was set, use the configured default (from the default scope), or the hard-coded
+ // "default default" if none was set.
+ mode = DEFAULT_PREFERENCES_SCOPE.get(TEXT_CONFLICT_RESOLUTION_MODE, DEFAULT_TEXT_CONFLICT_RESOLUTION_MODE.name());
+ }
+ try {
+ return ConflictResolutionMode.valueOf(mode);
+ } catch (@SuppressWarnings("unused") IllegalArgumentException iae) {
+ return DEFAULT_TEXT_CONFLICT_RESOLUTION_MODE;
+ }
+ }
+
+ /**
+ * Sets the state of the debug mode.
+ *
+ * @param mode
+ * the conflict resolution mode to use.
+ */
+ public static void setTextConflictResolutionMode(ConflictResolutionMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException();
+ } else {
+ PREFERENCES_SCOPE.put(TEXT_CONFLICT_RESOLUTION_MODE, mode.name());
+ save();
+ }
+ }
+
+ /**
+ * Save the modification of the preference store.
+ */
+ private static void save() {
+ try {
+ PREFERENCES_SCOPE.flush();
+ } catch (BackingStoreException e) {
+ EEFIdeUiPlugin.getPlugin().error(e.getMessage(), e);
+ }
+ }
+}
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 1560e0d56..9c5994c58 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
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2015, 2016 Obeo.
+ * Copyright (c) 2015, 2017 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
@@ -31,6 +31,8 @@ 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;
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;
@@ -46,9 +48,11 @@ import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
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;
@@ -58,6 +62,25 @@ import org.eclipse.ui.forms.widgets.FormToolkit;
* @author sbegaudeau
*/
public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
+ /**
+ * The different ways an edition conflict can be resolved. Used by the default implementation of
+ * {@link EEFTextLifecycleManager#resolveEditionConflict(Shell, String, String, String)}.
+ */
+ public static enum ConflictResolutionMode {
+ /**
+ * Use the version being edited in the widget, overriding the new version computed from the current model state.
+ */
+ USE_LOCAL_VERSION,
+ /**
+ * Use the version computed from the current model state, replacing the text being edited by the user in the
+ * widget.
+ */
+ USE_MODEL_VERSION,
+ /**
+ * Ask the user through a simple dialog which version to keep.
+ */
+ ASK_USER
+ }
/**
* This constant is used in order to tell SWT that the text area should be 300px wide even if it is not useful. The
@@ -67,12 +90,6 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
private static final int TEXT_AREA_WIDTH_HINT = 300;
/**
- * Feature flag to enable experimental workaround for the text recovery in case of concurrent refresh.
- */
- private static final boolean FLAG_RECOVER_TEXT_INPUT = "true" //$NON-NLS-1$
- .equals(System.getProperty("org.eclipse.eef.experimental.recoverLostTextInput", "false")); //$NON-NLS-1$ //$NON-NLS-2$
-
- /**
* The description.
*/
private EEFTextDescription description;
@@ -318,15 +335,9 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
this.focusListener = new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
- if (FLAG_RECOVER_TEXT_INPUT) {
- if (!EEFTextLifecycleManager.this.lockedByOtherInProgress.get() && !EEFTextLifecycleManager.this.container.isRenderingInProgress()
- && EEFTextLifecycleManager.this.isDirty) {
- EEFTextLifecycleManager.this.updateValue(false);
- }
- } else {
- if (!EEFTextLifecycleManager.this.container.isRenderingInProgress() && EEFTextLifecycleManager.this.isDirty) {
- EEFTextLifecycleManager.this.updateValue(false);
- }
+ if (!EEFTextLifecycleManager.this.lockedByOtherInProgress.get() && !EEFTextLifecycleManager.this.container.isRenderingInProgress()
+ && EEFTextLifecycleManager.this.isDirty) {
+ EEFTextLifecycleManager.this.updateValue(false);
}
}
@@ -389,32 +400,14 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
if (m != null) {
boolean resettingToPreviousReferenceValue = equals(newDisplayText[0], m.referenceValue);
boolean userHasUncommitedInput = !equals(newDisplayText[0], m.userInput);
- if (FLAG_RECOVER_TEXT_INPUT && m.appliesTo(EEFTextLifecycleManager.this) && userHasUncommitedInput) {
+ if (m.appliesTo(EEFTextLifecycleManager.this) && userHasUncommitedInput) {
if (resettingToPreviousReferenceValue) {
// Custom user input overrides resetting the same previous referenceValue.
newDisplayText[0] = m.userInput;
- } else {
- // The new model state produces a different value than the one the user saw when he
- // started
- // editing.
- String msg = "The model has changed in an incompatible way while you were editing this widget.\nNew value from the model: {0}\nEdited version: {1}"; //$NON-NLS-1$
- String[] choices = { "Use new value from the model", "Keep the edited version" }; //$NON-NLS-1$ //$NON-NLS-2$
- // Can not use MessageDialog.open(kind, parent, title, message, style,
- // dialogButtonLabels)
- // with Neon...
- MessageDialog dialog = new MessageDialog(EEFTextLifecycleManager.this.text.getShell(), "Model Changed During Edition", //$NON-NLS-1$
- null, MessageFormat.format(msg, newDisplayText[0], m.userInput), MessageDialog.QUESTION, 0, choices);
- switch (dialog.open()) {
- case 0:
- // Nothing to do, newDisplayText[0] is initialized with the value from the model.
- break;
- case 1:
- // Override with user input.
- newDisplayText[0] = m.userInput;
- break;
- default:
- throw new IllegalStateException();
- }
+ } else if (!equals(m.userInput, newDisplayText[0])) {
+ // Conflict must be resolved somehow.
+ newDisplayText[0] = resolveEditionConflict(EEFTextLifecycleManager.this.text.getShell(), m.referenceValue, m.userInput,
+ newDisplayText[0]);
}
}
Memento.remove(text);
@@ -423,6 +416,84 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
}
/**
+ * Handle conflicts between un-commited changes in the widget and concurrent changes in the model that produce a
+ * different value than the original one seen by the user.
+ *
+ * @param shell
+ * the shell to use if user interaction is needed.
+ * @param originalValue
+ * the original, common value, before the user started editing and before the concurrent model change
+ * produced a new text.
+ * @param localEditedVersion
+ * the value as edited by the user, and seen in the UI.
+ * @param newValueFromModel
+ * the new value produced from the new model state.
+ * @return the new value to use in the text field.
+ */
+ protected String resolveEditionConflict(Shell shell, String originalValue, String localEditedVersion, final String newValueFromModel) {
+ String result;
+ switch (EEFPreferences.getTextConflictResolutionMode()) {
+ case USE_LOCAL_VERSION:
+ result = localEditedVersion;
+ break;
+ case USE_MODEL_VERSION:
+ result = newValueFromModel;
+ break;
+ case ASK_USER:
+ result = askUserToResolveConflict(shell, originalValue, localEditedVersion, newValueFromModel);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ return result;
+ }
+
+ /**
+ * Open a simple dialog to inform the user of a conflict and ask him which version to keep.
+ *
+ * @param shell
+ * the shell to use if user interaction is needed.
+ * @param originalValue
+ * the original, common value, before the user started editing and before the concurrent model change
+ * produced a new text.
+ * @param localEditedVersion
+ * the value as edited by the user, and seen in the UI.
+ * @param newValueFromModel
+ * the new value produced from the new model state.
+ * @return the value chosen by the user.
+ */
+ protected String askUserToResolveConflict(final Shell shell, String originalValue, final String localEditedVersion,
+ final String newValueFromModel) {
+ final String[] result = { localEditedVersion };
+ // @formatter:off
+ final String[] choices = {
+ Messages.EEFTextLifecycleManager_conflictDialog_choiceNewModelValue,
+ Messages.EEFTextLifecycleManager_conflictDialog_choiceLocalEditedValue,
+ };
+ // @formatter:on
+ shell.getDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ Image img = shell.getDisplay().getSystemImage(SWT.ICON_QUESTION);
+ MessageDialog dialog = new MessageDialog(shell, Messages.EEFTextLifecycleManager_conflictDialog_title, img,
+ MessageFormat.format(Messages.EEFTextLifecycleManager_conflictDialog_message, newValueFromModel, localEditedVersion),
+ MessageDialog.QUESTION, 0, choices);
+ switch (dialog.open()) {
+ case 0:
+ result[0] = newValueFromModel;
+ break;
+ case 1:
+ result[0] = localEditedVersion;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ });
+ return result[0];
+ }
+
+ /**
* Tests two objects for equality, considering <code>null</code>.
*
* @param o1
@@ -516,15 +587,11 @@ public class EEFTextLifecycleManager extends AbstractEEFWidgetLifecycleManager {
@Override
protected void lockedByOther() {
- if (FLAG_RECOVER_TEXT_INPUT) {
- this.lockedByOtherInProgress.set(true);
- try {
- super.lockedByOther();
- } finally {
- this.lockedByOtherInProgress.set(false);
- }
- } else {
+ this.lockedByOtherInProgress.set(true);
+ try {
super.lockedByOther();
+ } finally {
+ this.lockedByOtherInProgress.set(false);
}
}

Back to the top