Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/Bind.java256
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/DefaultValueBinding.java76
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IBidiConverter.java55
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IModelBinding.java28
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayBinding.java92
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayModelBinding.java23
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITargetBinding.java22
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITwoWayBinding.java73
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayBinding.java157
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayConversionBinding.java43
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayBinding.java177
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayConversionBinding.java71
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayValidationBinding.java80
14 files changed, 1154 insertions, 0 deletions
diff --git a/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
index f2fdfc8a..94416b0d 100644
--- a/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
@@ -11,6 +11,7 @@ Export-Package: org.eclipse.core.databinding,
org.eclipse.core.databinding.validation;x-internal:=false,
org.eclipse.core.internal.databinding;x-friends:="org.eclipse.core.databinding.beans",
org.eclipse.core.internal.databinding.conversion;x-friends:="org.eclipse.jface.tests.databinding",
+ org.eclipse.core.internal.databinding.provisional.bind;x-internal:=true,
org.eclipse.core.internal.databinding.validation;x-friends:="org.eclipse.jface.tests.databinding"
Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.2.0,4.0.0)",
org.eclipse.core.databinding.observable;bundle-version="[1.3.0,2.0.0)";visibility:=reexport,
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/Bind.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/Bind.java
new file mode 100644
index 00000000..0c5ed903
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/Bind.java
@@ -0,0 +1,256 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @since 1.5
+ * @provisional This class has been added as part of a work in progress. It is
+ * not guaranteed to work or remain the same in future releases.
+ * For more information contact e4-dev@eclipse.org.
+ */
+public class Bind {
+
+ /**
+ * This is the ITwoWayBinding that sits immediately on top on the model
+ * observable.
+ *
+ * @param <V>
+ */
+ static class TwoWayModelBinding<V> extends TwoWayBinding<V> {
+ private final IObservableValue<V> modelObservable;
+
+ private final boolean takeOwnership;
+
+ private boolean isModelChanging = false;
+
+ IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+ public void handleValueChange(ValueChangeEvent<V> event) {
+ if (!isModelChanging) {
+ targetBinding.setTargetValue(event.diff.getNewValue());
+ }
+ }
+ };
+
+ /**
+ *
+ * @param modelObservable
+ * @param takeOwnership
+ * <code>true</code> if this class is to take ownership of
+ * the given model observable, which means it has the
+ * responsibility of disposing of it when this binding is
+ * disposed, <code>false</code> if the given model observable
+ * must not be disposed of by this class
+ */
+ public TwoWayModelBinding(IObservableValue<V> modelObservable,
+ boolean takeOwnership) {
+ super(true);
+ this.modelObservable = modelObservable;
+ this.takeOwnership = takeOwnership;
+
+ modelObservable.addValueChangeListener(modelListener);
+ }
+
+ public V getModelValue() {
+ return modelObservable.getValue();
+ }
+
+ public void setModelValue(V newValue) {
+ isModelChanging = true;
+ try {
+ modelObservable.setValue(newValue);
+ } finally {
+ isModelChanging = false;
+ }
+ }
+
+ public void removeModelListener() {
+ modelObservable.removeValueChangeListener(modelListener);
+
+ /*
+ * If we 'own' the observable then we must dispose of it, if we
+ * don't 'own' the observable then we must not dispose of it.
+ */
+ if (takeOwnership) {
+ modelObservable.dispose();
+ }
+ }
+ }
+
+ /**
+ * This is the IOneWayBinding that sits immediately on top on the model
+ * observable.
+ *
+ * @param <V>
+ */
+ static class OneWayModelBinding<V> extends OneWayBinding<V> {
+ private final IObservableValue<V> modelObservable;
+
+ private final boolean takeOwnership;
+
+ IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+ public void handleValueChange(ValueChangeEvent<V> event) {
+ targetBinding.setTargetValue(event.diff.getNewValue());
+ }
+ };
+
+ /**
+ *
+ * @param modelObservable
+ * @param takeOwnership
+ * <code>true</code> if this class is to take ownership of
+ * the given model observable, which means it has the
+ * responsibility of disposing of it when this binding is
+ * disposed, <code>false</code> if the given model observable
+ * must not be disposed of by this class
+ */
+ public OneWayModelBinding(IObservableValue<V> modelObservable,
+ boolean takeOwnership) {
+ this.modelObservable = modelObservable;
+ this.takeOwnership = takeOwnership;
+
+ modelObservable.addValueChangeListener(modelListener);
+ }
+
+ public V getModelValue() {
+ return modelObservable.getValue();
+ }
+
+ public void removeModelListener() {
+ modelObservable.removeValueChangeListener(modelListener);
+
+ /*
+ * If we 'own' the observable then we must dispose of it, if we
+ * don't 'own' the observable then we must not dispose of it.
+ */
+ if (takeOwnership) {
+ modelObservable.dispose();
+ }
+ }
+ }
+
+ /**
+ * Initiates two-way binding.
+ * <P>
+ * The model observable is not disposed when this binding is disposed.
+ *
+ * @param modelObservable
+ * @return an object that can chain one-way bindings
+ */
+ public static <V> IOneWayBinding<V> oneWay(
+ IObservableValue<V> modelObservable) {
+ return new OneWayModelBinding<V>(modelObservable, false);
+ }
+
+ /**
+ * This is a convenience method that creates an observable for the model
+ * from the given property and source. The observable will be disposed when
+ * the binding is disposed.
+ *
+ * @param modelProperty
+ * @param source
+ * @return an object that can chain one-way bindings
+ */
+ public static <S, V> IOneWayBinding<V> oneWay(
+ IValueProperty<S, V> modelProperty, S source) {
+ IObservableValue<V> modelObservable = modelProperty.observe(source);
+ return new OneWayModelBinding<V>(modelObservable, true);
+ }
+
+ /**
+ * Initiates two-way binding.
+ * <P>
+ * The model observable is not disposed when this binding is disposed.
+ *
+ * @param modelObservable
+ * @return an object that can chain two-way bindings
+ */
+ public static <V> ITwoWayBinding<V> twoWay(
+ IObservableValue<V> modelObservable) {
+ return new TwoWayModelBinding<V>(modelObservable, false);
+ }
+
+ /**
+ * This is a convenience method that creates an observable for the model
+ * from the given property and source. The observable will be disposed when
+ * the binding is disposed.
+ *
+ * @param modelProperty
+ * @param source
+ * @return an object that can chain two-way bindings
+ */
+ public static <S, V> ITwoWayBinding<V> twoWay(
+ IValueProperty<S, V> modelProperty, S source) {
+ IObservableValue<V> modelObservable = modelProperty.observe(source);
+ return new TwoWayModelBinding<V>(modelObservable, true);
+ }
+
+ /**
+ * This method is used to 'bounce back' a value from the target.
+ * Specifically this means whenever the target value changes, the value is
+ * converted using the given converter (targetToModel). The resulting value
+ * is the converted back using the same converter (modelToTarget).
+ * <P>
+ * A use case for this method is as follows. You have a number that is
+ * stored in the model as an Integer. You want to display the number in a
+ * text box with separators, so 1234567 would be displayed as 1,234,567 or
+ * 1.234.567 depending on your regional settings. You want to allow more
+ * flexibility on what the user can enter. For example you may want to allow
+ * the user to miss out the separators. As the user types, the value is
+ * updated in the model. When the control loses focus you want the
+ * separators to be inserted in the Text control in the proper positions.
+ * <P>
+ * To do this you create two bindings. One is a two-way binding that
+ * observes the Text control with SWT.Modify. The other is a 'bounce back'
+ * that observes the control with SWT.FocusOut. It might be coded as
+ * follows:
+ * <P>
+ * <code>
+ * Bind.bounceBack(myIntegerToTextConverter)
+ .to(SWTObservables.observeText(textControl, SWT.FocusOut));
+ * </code>
+ *
+ * @param converter
+ * @return an object that can chain two-way bindings
+ */
+ public static <T1, T2> ITwoWayBinding<T2> bounceBack(
+ final IBidiConverter<T1, T2> converter) {
+ return new TwoWayBinding<T2>(false) {
+
+ public T2 getModelValue() {
+ /*
+ * This method should never be called because pullInitialValue
+ * is set to false.
+ */
+ throw new UnsupportedOperationException();
+ }
+
+ public void setModelValue(T2 valueFromTarget) {
+ try {
+ T1 modelSideValue = converter
+ .targetToModel(valueFromTarget);
+ T2 valueBackToTarget = converter
+ .modelToTarget(modelSideValue);
+ this.targetBinding.setTargetValue(valueBackToTarget);
+ } catch (CoreException e) {
+ /*
+ * No bounce-back occurs if the value from the target side
+ * cannot be converted. We do nothing because the user will
+ * typically have an error indicator anyway.
+ */
+ }
+ }
+
+ public void removeModelListener() {
+ /*
+ * Nothing to do here because nothing originates from the model
+ * side.
+ */
+ }
+ };
+ }
+
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/DefaultValueBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/DefaultValueBinding.java
new file mode 100644
index 00000000..d770daa3
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/DefaultValueBinding.java
@@ -0,0 +1,76 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ *
+ * @param <T1>
+ */
+public class DefaultValueBinding<T1> extends TwoWayBinding<T1> implements
+ ITargetBinding<T1> {
+
+ private final IOneWayModelBinding<T1> modelBinding;
+
+ private boolean stopped = false;
+
+ /**
+ * @param modelBinding
+ */
+ public DefaultValueBinding(IOneWayModelBinding<T1> modelBinding) {
+ super(true);
+ this.modelBinding = modelBinding;
+ }
+
+ public T1 getModelValue() {
+ return modelBinding.getModelValue();
+ }
+
+ public void setTargetValue(T1 valueOnModelSide) {
+ targetBinding.setTargetValue(valueOnModelSide);
+ }
+
+ public void setStatus(IStatus status) {
+ /*
+ * Generally there are no status values sent from the model to the
+ * target because the model is generally valid. However there may be
+ * cases when error or warning statuses come from the model. For example
+ * when using JSR-303 validations the validation is done on the value in
+ * the model object. In any case we just pass it on.
+ */
+ targetBinding.setStatus(status);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.databinding.bind.IModelBinding#setModelValue(java.lang
+ * .Object)
+ */
+ public void setModelValue(T1 newValue) {
+ /*
+ * The target has changed so stop this binding. The target will continue
+ * to notify us of changes for as long as it exists so we need to set a
+ * flag to indicate that this binding is in a stopped state.
+ */
+ if (!stopped) {
+ stopped = true;
+ modelBinding.removeModelListener();
+ }
+ }
+
+ public void removeModelListener() {
+ /*
+ * Pass the request back to the next link in the binding chain so
+ * eventually the request gets back to the model observable.
+ *
+ * Note that if we are in a 'stopped' state then the listener has
+ * already been removed from the model and we should not attempt to
+ * remove it again.
+ */
+ if (!stopped) {
+ modelBinding.removeModelListener();
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IBidiConverter.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IBidiConverter.java
new file mode 100644
index 00000000..3024ac34
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IBidiConverter.java
@@ -0,0 +1,55 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Interface for bi-directional conversions.
+ * <P>
+ * Bi-directional conversions encapsulate both the target-to-model and the
+ * model-to-target conversions in a single converter. This simplifies the API
+ * for two-way binding as only one converter need be specified. It also helps to
+ * ensure consistency in the conversions.
+ * <P>
+ * The two conversions must be consistent with each other. More specifically,
+ * for any value of type T1, call modelToTarget to get a type T2, pass that
+ * value to targetToModel to get a type T1, pass that value to modelToTarget to
+ * get a type T2. The value returned by the second call to modelToTarget must be
+ * 'equal' to the value returned by the first call to modelToTarget ('equal'
+ * meaning the 'equal' method returns 'true'). Likewise, for any value of type
+ * T2, call targetToModel to get a type T1, pass that value to modelToTarget to
+ * get a type T2, pass that value to targetToModel to get a type T1. The value
+ * returned by the second call to targetToModel must be 'equal' to the value
+ * returned by the first call to targetToModel.
+ * <P>
+ * Note that the above rules allow the conversion to start with non-canonical
+ * forms but the first conversion must always result in a canonical form
+ * (meaning if the value is conceptually the same then it must be 'equal'. For
+ * example, "12,345.300" may be allowed as the target, this being converted to a
+ * BigDecimal with a value of 12345.30 in the model, being converted back to
+ * "12345.30" in the target. This does not 'equal' the original target value but
+ * converting this back again to the model data type must result in a BigDecimal
+ * that 'equals' the BigDecimal obtained from the first conversion.
+ *
+ * @since 1.5
+ * @param <T1>
+ * the data type on the model side
+ * @param <T2>
+ * the data type on the target side
+ */
+public interface IBidiConverter<T1, T2> {
+
+ /**
+ * @param fromObject
+ * @return the value converted for use on the target side
+ */
+ T2 modelToTarget(T1 fromObject);
+
+ /**
+ * @param fromObject
+ * @return the value converted for use on the model side
+ * @throws CoreException
+ * if the value cannot be converted
+ */
+ T1 targetToModel(T2 fromObject) throws CoreException;
+
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IModelBinding.java
new file mode 100644
index 00000000..77e3da46
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IModelBinding.java
@@ -0,0 +1,28 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+/**
+ * This interface is used internally by this package to manage two-way binding
+ * to the model observable.
+ *
+ * @since 1.5
+ * @noimplement
+ * @param <T>
+ */
+interface IModelBinding<T> {
+
+ /**
+ * @return the model from the model side
+ */
+ T getModelValue();
+
+ /**
+ * @param newValue
+ */
+ void setModelValue(T newValue);
+
+ /**
+ * Removes the listener from the model. This method is called when the
+ * target is disposed.
+ */
+ void removeModelListener();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayBinding.java
new file mode 100644
index 00000000..00186042
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayBinding.java
@@ -0,0 +1,92 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+
+/**
+ * This interface is used to chain together one-way binding.
+ * <P>
+ * To create a one-way binding, first create an implementation of this interface
+ * from an observable on the model value, e.g.: <code>
+ * Bind.oneWay(modelObservable)
+ * </code> then call methods on this interface to perform conversions or other
+ * supported operations. Each of these methods returns an implementation of this
+ * interface so these operations can be chained together. Finally call the
+ * <code>to</code> method to bind to the target observable.
+ *
+ * @since 1.5
+ * @param <T1>
+ */
+public interface IOneWayBinding<T1> {
+
+ /**
+ * @param converter
+ * @return the value converted to the type expected by the next part of the
+ * binding chain
+ */
+ <T2> IOneWayBinding<T2> convert(IConverter<T1, T2> converter);
+
+ /**
+ * This method is similar to <code>convert</code>. However if any
+ * observables are read during the conversion then listeners are added to
+ * these observables and the conversion is done again.
+ * <P>
+ * The conversion is always repeated keeping the same value of the model. It
+ * is assumed that the tracked observables affect the target. For example
+ * suppose a time widget contains a time which is bound to a Date property
+ * in the model. The time zone to use is a preference and an observable
+ * exists for the time zone (which would implement
+ * IObservableValue<TimeZone>). If the user changes the time zone in the
+ * preferences then the text in the time widget will change to show the same
+ * time but in a different time zone. The time in the model will not change
+ * when the time zone is changed. If the user edits the time in the time
+ * widget then that time will be interpreted using the new time zone and
+ * converted to a Date object for the model.
+ *
+ * @param converter
+ * @return an object that can chain one-way bindings
+ */
+ <T2> IOneWayBinding<T2> convertWithTracking(IConverter<T1, T2> converter);
+
+ /**
+ * When chaining together one-way binding operations, this method must be
+ * last. It binds to the target.
+ * <P>
+ * The target observable is not disposed when this binding is disposed.
+ *
+ * @param targetObservable
+ */
+ void to(IObservableValue<T1> targetObservable);
+
+ /**
+ * When chaining together one-way binding operations, this method must be
+ * last. It binds to the target.
+ *
+ * This is a convenience method that creates an observable for the target
+ * from the given property and source. The observable will be disposed when
+ * the binding is disposed.
+ *
+ * @param targetProperty
+ * @param source
+ */
+ <S> void to(IValueProperty<S, T1> targetProperty, S source);
+
+ /**
+ * This method is used to create a one-way binding from the model to the
+ * target but the binding stops if something else changes the target.
+ * <P>
+ * A use case is when a default value is provided in a UI control. For
+ * example suppose you have two fields, 'amount' and 'sales tax'. When the
+ * user enters an amount, the requirement is that the 'sales tax' field is
+ * completed based on the amount using a given tax rate. If the user edits
+ * the amount, the sales tax amount changes accordingly. However once the
+ * user edits the sales tax field then changes to the amount field no longer
+ * affect the sales tax field.
+ *
+ * @return an object that can chain two-way bindings (although this is a
+ * one-way binding, the binding onwards to the target must be
+ * two-way so that we know when the user changes the target)
+ */
+ ITwoWayBinding<T1> untilTargetChanges();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayModelBinding.java
new file mode 100644
index 00000000..31fc8006
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/IOneWayModelBinding.java
@@ -0,0 +1,23 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+/**
+ * This interface is used internally by this package to manage one-way binding
+ * from the model observable.
+ *
+ * @since 1.5
+ * @noimplement
+ * @param <T>
+ */
+interface IOneWayModelBinding<T> {
+
+ /**
+ * @return the value from the model side
+ */
+ T getModelValue();
+
+ /**
+ * Removes the listener from the model. This method is called when the
+ * target is disposed.
+ */
+ void removeModelListener();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITargetBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITargetBinding.java
new file mode 100644
index 00000000..b8c4bf94
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITargetBinding.java
@@ -0,0 +1,22 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ *
+ * @param <F>
+ */
+public interface ITargetBinding<F> {
+ /**
+ * @param targetValue
+ */
+ void setTargetValue(F targetValue);
+
+ /**
+ * Push the error status back to the target
+ *
+ * @param status
+ */
+ void setStatus(IStatus status);
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITwoWayBinding.java
new file mode 100644
index 00000000..a6e1245b
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/ITwoWayBinding.java
@@ -0,0 +1,73 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+
+/**
+ * This interface is used to chain together two-way binding.
+ * <P>
+ * To create a two-way binding, first create an implementation of this interface
+ * from an observable on the model value, e.g.: <code>
+ * Bind.twoWay(modelObservable)
+ * </code> then call methods on this interface to perform conversions or other
+ * supported operations. Each of these methods returns an implementation of this
+ * interface so these operations can be chained together. Finally call the
+ * <code>to</code> method to bind to the target observable.
+ *
+ * @since 1.5
+ * @param <T1>
+ */
+public interface ITwoWayBinding<T1> {
+
+ /**
+ * @param converter
+ * @return an object that can chain two-way bindings
+ */
+ <T2> ITwoWayBinding<T2> convert(final IBidiConverter<T1, T2> converter);
+
+ /**
+ * This method is similar to <code>convert</code>. However if any
+ * observables are read during the conversion then listeners are added to
+ * these observables and the conversion is done again.
+ * <P>
+ * The conversion is always repeated keeping the same value of the model. It
+ * is assumed that the tracked observables affect the target. For example
+ * suppose a time widget contains a time which is bound to a Date property
+ * in the model. The time zone to use is a preference and an observable
+ * exists for the time zone (which would implement
+ * IObservableValue<TimeZone>). If the user changes the time zone in the
+ * preferences then the text in the time widget will change to show the same
+ * time but in a different time zone. The time in the model will not change
+ * when the time zone is changed. If the user edits the time in the time
+ * widget then that time will be interpreted using the new time zone and
+ * converted to a Date object for the model.
+ *
+ * @param converter
+ * @return an object that can chain two-way bindings
+ */
+ <T2> ITwoWayBinding<T2> convertWithTracking(IBidiConverter<T1, T2> converter);
+
+ /**
+ * When chaining together two-way binding operations, this method must be
+ * last. It binds to the target observable.
+ * <P>
+ * The target observable is not disposed when this binding is disposed.
+ *
+ * @param targetObservable
+ */
+ void to(IObservableValue<T1> targetObservable);
+
+ /**
+ * When chaining together two-way binding operations, this method must be
+ * last. It binds to the target.
+ *
+ * This is a convenience method that creates an observable for the target
+ * from the given property and source. The observable will be disposed when
+ * the binding is disposed.
+ *
+ * @param targetProperty
+ * @param source
+ */
+ <S> void to(IValueProperty<S, T1> targetProperty, S source);
+
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayBinding.java
new file mode 100644
index 00000000..c8538dd5
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayBinding.java
@@ -0,0 +1,157 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ *
+ * @param <T2>
+ */
+public abstract class OneWayBinding<T2> implements IOneWayBinding<T2>,
+ IOneWayModelBinding<T2> {
+
+ protected ITargetBinding<T2> targetBinding;
+
+ public <T3> IOneWayBinding<T3> convert(final IConverter<T2, T3> converter) {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ OneWayConversionBinding<T3, T2> nextBinding = new OneWayConversionBinding<T3, T2>(
+ this, converter);
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ /**
+ * This method is similar to <code>convert</code>. However if any
+ * observables are read during the conversion then listeners are added to
+ * these observables and the conversion is done again.
+ * <P>
+ * The conversion is always repeated keeping the same value of the model. It
+ * is assumed that the tracked observables affect the target. For example
+ * suppose a time widget contains a time which is bound to a Date property
+ * in the model. The time zone to use is a preference and an observable
+ * exists for the time zone (which would implement
+ * IObservableValue<TimeZone>). If the user changes the time zone in the
+ * preferences then the text in the time widget will change to show the same
+ * time but in a different time zone. The time in the model will not change
+ * when the time zone is changed. If the user edits the time in the time
+ * widget then that time will be interpreted using the new time zone and
+ * converted to a Date object for the model.
+ *
+ * @param converter
+ * @return an object that can chain one-way bindings
+ */
+ public <T3> IOneWayBinding<T3> convertWithTracking(
+ final IConverter<T2, T3> converter) {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ OneWayConversionBinding<T3, T2> nextBinding = new OneWayConversionBinding<T3, T2>(
+ this, converter);
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ /**
+ * This method creates a one-way binding from model to target that stops if
+ * someone else edits the target.
+ * <P>
+ * This method is used to provide a default value. The default value is a
+ * one-way binding, typically a one-way binding from a ComputedValue. This
+ * default value is the receiver of this method. The target may be either an
+ * observable on the model or an observable on a UI control. (If you have
+ * two-way binding between the model and the UI control then you can bind a
+ * default value to either but you will typically have fewer conversions to
+ * do if you bind to the model).
+ * <P>
+ * The binding on to the target is a two-way binding. The default value is
+ * passed on to the target until such time that the target is changed by
+ * someone other than ourselves. At that point the binding stops and changes
+ * in the default value are no longer set into the target.
+ * <P>
+ * An example use is as follows: There is a field in which the user can
+ * enter the price of an item. There is another field in which the user can
+ * enter the sales tax for the sale of the item. We want the sales tax field
+ * to automatically calculate to be 5% of the price. If the user edits the
+ * price then the sales tax field should change too. The user may edit the
+ * sales tax field. If the user does this then the sales tax field will no
+ * longer be updated as the price changes.
+ */
+ public ITwoWayBinding<T2> untilTargetChanges() {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ DefaultValueBinding<T2> nextBinding = new DefaultValueBinding<T2>(this);
+
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ public void to(final IObservableValue<T2> targetObservable) {
+ /*
+ * We have finally made it to the target observable.
+ *
+ * Initially set the target observable to the current value from the
+ * model.
+ */
+ targetObservable.setValue(getModelValue());
+
+ /*
+ * The target binding contains a method that is called whenever a new
+ * value comes from the model side. We simply set a target binding that
+ * sets that value into the target observable.
+ */
+ targetBinding = new ITargetBinding<T2>() {
+ public void setTargetValue(T2 targetValue) {
+ targetObservable.setValue(targetValue);
+ }
+
+ public void setStatus(IStatus status) {
+ /*
+ * Generally there are no status values sent from the model to
+ * the target because the model is generally valid. However
+ * there may be cases when error or warning statuses come from
+ * the model. For example when using JSR-303 validations the
+ * validation is done on the value in the model object. In any
+ * case, there is no status observable provided by the user so
+ * we drop it.
+ */
+ }
+ };
+
+ /*
+ * If the target is disposed, be sure to remove the listener from the
+ * model.
+ */
+ targetObservable.addDisposeListener(new IDisposeListener() {
+ public void handleDispose(DisposeEvent event) {
+ removeModelListener();
+ }
+ });
+ }
+
+ public <S> void to(IValueProperty<S, T2> targetProperty, S source) {
+ IObservableValue<T2> targetObservable = targetProperty.observe(source);
+ to(targetObservable);
+ // TODO dispose observable if binding is disposed
+ }
+
+ /**
+ * @param status
+ */
+ public void setStatus(IStatus status) {
+ targetBinding.setStatus(status);
+ }
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayConversionBinding.java
new file mode 100644
index 00000000..dbb85113
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/OneWayConversionBinding.java
@@ -0,0 +1,43 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+
+/**
+ * @since 1.5
+ *
+ * @param <T2>
+ * @param <T1>
+ */
+public class OneWayConversionBinding<T2, T1> extends OneWayBinding<T2>
+ implements ITargetBinding<T1> {
+ private final IOneWayModelBinding<T1> modelBinding;
+ private final IConverter<T1, T2> converter;
+
+ /**
+ * @param modelBinding
+ * @param converter
+ */
+ public OneWayConversionBinding(IOneWayModelBinding<T1> modelBinding,
+ IConverter<T1, T2> converter) {
+ this.modelBinding = modelBinding;
+ this.converter = converter;
+ }
+
+ public T2 getModelValue() {
+ T1 modelValue = modelBinding.getModelValue();
+ return converter.convert(modelValue);
+ }
+
+ public void setTargetValue(T1 valueOnModelSide) {
+ T2 valueOnTargetSide = converter.convert(valueOnModelSide);
+ targetBinding.setTargetValue(valueOnTargetSide);
+ }
+
+ public void removeModelListener() {
+ /*
+ * Pass the request back to the next link in the binding chain so
+ * eventually the request gets back to the model observable.
+ */
+ modelBinding.removeModelListener();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayBinding.java
new file mode 100644
index 00000000..4577664e
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayBinding.java
@@ -0,0 +1,177 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+import org.eclipse.core.databinding.validation.IValidator;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ *
+ * @param <T2>
+ */
+public abstract class TwoWayBinding<T2> implements ITwoWayBinding<T2>,
+ IModelBinding<T2> {
+
+ /**
+ * <code>true</code> if the target observable bound by the <code>to</code>
+ * method is to be initially set to the value from the model side,
+ * <code>false</code> if its value is to be set only when changes are pushed
+ * from the target side
+ */
+ protected boolean pullInitialValue;
+
+ protected ITargetBinding<T2> targetBinding;
+
+ /**
+ * @param pullInitialValue
+ * <code>true</code> if the initial value from the model is to be
+ * set into the target, <code>false</code> if the target must not
+ * be set until the model value changes
+ */
+ public TwoWayBinding(boolean pullInitialValue) {
+ this.pullInitialValue = pullInitialValue;
+ }
+
+ public <T3> ITwoWayBinding<T3> convert(
+ final IBidiConverter<T2, T3> converter) {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ TwoWayConversionBinding<T3, T2> nextBinding = new TwoWayConversionBinding<T3, T2>(
+ this, converter, pullInitialValue);
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ /**
+ * This method is similar to <code>convert</code>. However if any
+ * observables are read during the conversion then listeners are added to
+ * these observables and the conversion is done again.
+ * <P>
+ * The conversion is always repeated keeping the same value of the model. It
+ * is assumed that the tracked observables affect the target. For example
+ * suppose a time widget contains a time which is bound to a Date property
+ * in the model. The time zone to use is a preference and an observable
+ * exists for the time zone (which would implement
+ * IObservableValue<TimeZone>). If the user changes the time zone in the
+ * preferences then the text in the time widget will change to show the same
+ * time but in a different time zone. The time in the model will not change
+ * when the time zone is changed. If the user edits the time in the time
+ * widget then that time will be interpreted using the new time zone and
+ * converted to a Date object for the model.
+ *
+ * @param converter
+ * @return an object that can chain two-way bindings
+ */
+ public <T3> ITwoWayBinding<T3> convertWithTracking(
+ final IBidiConverter<T2, T3> converter) {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ TwoWayConversionBinding<T3, T2> nextBinding = new TwoWayConversionBinding<T3, T2>(
+ this, converter, pullInitialValue);
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ /**
+ * @param validator
+ * @return an object that can chain two-way bindings
+ */
+ public ITwoWayBinding<T2> validate(final IValidator<T2> validator) {
+ if (targetBinding != null) {
+ throw new RuntimeException(
+ "When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+ }
+
+ TwoWayValidationBinding<T2> nextBinding = new TwoWayValidationBinding<T2>(
+ this, validator, pullInitialValue);
+ targetBinding = nextBinding;
+ return nextBinding;
+ }
+
+ public void to(final IObservableValue<T2> targetObservable) {
+ to(targetObservable, null);
+ }
+
+ public <S> void to(IValueProperty<S, T2> targetProperty, S source) {
+ IObservableValue<T2> targetObservable = targetProperty.observe(source);
+ to(targetObservable);
+ // TODO dispose observable if binding is disposed
+ }
+
+ /**
+ * We have finally made it to the target observable.
+ *
+ * Initially set the target observable to the current value from the model
+ * (if the pullInitialValue flag is set which it will be in most cases).
+ *
+ * @param targetObservable
+ * @param statusObservable
+ */
+ public void to(final IObservableValue<T2> targetObservable,
+ final IObservableValue<IStatus> statusObservable) {
+ if (pullInitialValue) {
+ targetObservable.setValue(getModelValue());
+ }
+
+ final boolean[] isChanging = new boolean[] { false };
+
+ /*
+ * The target binding contains a method that is called whenever a new
+ * value comes from the model side. We simply set a target binding that
+ * sets that value into the target observable.
+ */
+ targetBinding = new ITargetBinding<T2>() {
+ public void setTargetValue(T2 targetValue) {
+ try {
+ isChanging[0] = true;
+ targetObservable.setValue(targetValue);
+ } finally {
+ isChanging[0] = false;
+ }
+ }
+
+ public void setStatus(IStatus status) {
+ /*
+ * If there is a target for the status, set it. Otherwise drop
+ * it.
+ */
+ if (statusObservable != null) {
+ statusObservable.setValue(status);
+ }
+ }
+ };
+
+ /*
+ * Listen for changes originating from the target observable, and send
+ * those back through to the model side.
+ */
+ targetObservable.addValueChangeListener(new IValueChangeListener<T2>() {
+ public void handleValueChange(ValueChangeEvent<T2> event) {
+ if (!isChanging[0]) {
+ setModelValue(event.diff.getNewValue());
+ }
+ }
+ });
+
+ /*
+ * If the target is disposed, be sure to remove the listener from the
+ * model.
+ */
+ targetObservable.addDisposeListener(new IDisposeListener() {
+ public void handleDispose(DisposeEvent event) {
+ removeModelListener();
+ }
+ });
+ }
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayConversionBinding.java
new file mode 100644
index 00000000..2ac9ffec
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayConversionBinding.java
@@ -0,0 +1,71 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * @since 1.5
+ *
+ * @param <T2>
+ * @param <T1>
+ */
+public class TwoWayConversionBinding<T2, T1> extends TwoWayBinding<T2>
+ implements ITargetBinding<T1> {
+ private final IModelBinding<T1> modelBinding;
+ private final IBidiConverter<T1, T2> converter;
+
+ /**
+ * @param modelBinding
+ * @param converter
+ * @param pullInitialValue
+ */
+ public TwoWayConversionBinding(IModelBinding<T1> modelBinding,
+ IBidiConverter<T1, T2> converter, boolean pullInitialValue) {
+ super(pullInitialValue);
+ this.modelBinding = modelBinding;
+ this.converter = converter;
+ }
+
+ /**
+ * @return the value from the model, converted for use on the target side
+ */
+ public T2 getModelValue() {
+ T1 modelValue = modelBinding.getModelValue();
+ return converter.modelToTarget(modelValue);
+ }
+
+ /**
+ * @param valueOnTargetSide
+ */
+ public void setModelValue(T2 valueOnTargetSide) {
+ try {
+ T1 valueOnModelSide = converter.targetToModel(valueOnTargetSide);
+ modelBinding.setModelValue(valueOnModelSide);
+ targetBinding.setStatus(Status.OK_STATUS);
+ } catch (CoreException e) {
+ targetBinding.setStatus(e.getStatus());
+ }
+ }
+
+ public void setTargetValue(T1 valueOnModelSide) {
+ T2 valueOnTargetSide = converter.modelToTarget(valueOnModelSide);
+ targetBinding.setTargetValue(valueOnTargetSide);
+ }
+
+ public void setStatus(IStatus status) {
+ /*
+ * The error occurred somewhere on the model side. We push this error
+ * back to the target.
+ */
+ targetBinding.setStatus(status);
+ }
+
+ public void removeModelListener() {
+ /*
+ * Pass the request back to the next link in the binding chain so
+ * eventually the request gets back to the model observable.
+ */
+ modelBinding.removeModelListener();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayValidationBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayValidationBinding.java
new file mode 100644
index 00000000..fef7589b
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/provisional/bind/TwoWayValidationBinding.java
@@ -0,0 +1,80 @@
+package org.eclipse.core.internal.databinding.provisional.bind;
+
+import org.eclipse.core.databinding.validation.IValidator;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ *
+ * @param <T>
+ */
+public class TwoWayValidationBinding<T> extends TwoWayBinding<T> implements
+ ITargetBinding<T> {
+ private final IModelBinding<T> modelBinding;
+ private final IValidator<T> validator;
+
+ /**
+ * @param modelBinding
+ * @param validator
+ * @param pullInitialValue
+ */
+ public TwoWayValidationBinding(IModelBinding<T> modelBinding,
+ IValidator<T> validator, boolean pullInitialValue) {
+ super(pullInitialValue);
+ this.modelBinding = modelBinding;
+ this.validator = validator;
+ }
+
+ /**
+ * The default behavior is to validate only when going from target to model.
+ * The error status observable is assumed to be there to show user errors.
+ * Therefore no validation is done in this method.
+ *
+ * @return the value from the model
+ */
+ public T getModelValue() {
+ return modelBinding.getModelValue();
+ }
+
+ /**
+ * @param valueOnTargetSide
+ */
+ public void setModelValue(T valueOnTargetSide) {
+ IStatus status = validator.validate(valueOnTargetSide);
+ targetBinding.setStatus(status);
+
+ /*
+ * We pass on the value towards the model only if a warning or better.
+ * We block if an error or worse. This may or may not be the behavior
+ * expected by the users.
+ */
+ if (status.getSeverity() >= IStatus.WARNING) {
+ modelBinding.setModelValue(valueOnTargetSide);
+ }
+ }
+
+ /**
+ * The default behavior is to validate only when going from target to model.
+ * The error status observable is assumed to be there to show user errors.
+ * Therefore no validation is done in this method.
+ */
+ public void setTargetValue(T valueOnModelSide) {
+ targetBinding.setTargetValue(valueOnModelSide);
+ }
+
+ public void setStatus(IStatus status) {
+ /*
+ * The error occurred somewhere on the model side. We push this error
+ * back to the target.
+ */
+ targetBinding.setStatus(status);
+ }
+
+ public void removeModelListener() {
+ /*
+ * Pass the request back to the next link in the binding chain so
+ * eventually the request gets back to the model observable.
+ */
+ modelBinding.removeModelListener();
+ }
+} \ No newline at end of file

Back to the top