Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNigel Westbury2013-07-08 11:50:36 +0000
committerNigel Westbury2013-07-08 21:39:19 +0000
commit9ac8f9392c3863c13088aca4b6b681a2dbb18e7b (patch)
treeb82c8eeee29bfdc03852555acda55e38d7287ffd
parente6947e901ed8ec083ede35cd95f7630c6fcee80b (diff)
downloadorg.eclipse.e4.databinding-bind.tar.gz
org.eclipse.e4.databinding-bind.tar.xz
org.eclipse.e4.databinding-bind.zip
Bug 412499 - inital commit of new Bind methodsbind
-rw-r--r--bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java176
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java76
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java27
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java25
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java62
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java20
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java22
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java43
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java149
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java43
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java167
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java71
-rw-r--r--bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java80
14 files changed, 962 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..688997f5 100644
--- a/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@ Bundle-ClassPath: .
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.core.databinding,
+ org.eclipse.core.databinding.bind,
org.eclipse.core.databinding.conversion;x-internal:=false,
org.eclipse.core.databinding.validation;x-internal:=false,
org.eclipse.core.internal.databinding;x-friends:="org.eclipse.core.databinding.beans",
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java
new file mode 100644
index 00000000..d4ac643a
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java
@@ -0,0 +1,176 @@
+package org.eclipse.core.databinding.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.runtime.CoreException;
+
+/**
+ * @since 1.5
+ *
+ */
+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 boolean isModelChanging = false;
+
+ IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+ public void handleValueChange(ValueChangeEvent<V> event) {
+ if (!isModelChanging) {
+ targetBinding.setTargetValue(event.diff.getNewValue());
+ }
+ }
+ };
+
+ public TwoWayModelBinding(IObservableValue<V> modelObservable,
+ boolean pullInitialValue) {
+ super(pullInitialValue);
+ this.modelObservable = modelObservable;
+
+ 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() {
+ // Actually we don't own this observable so don't dispose,
+ // just remove our listener.
+ modelObservable.removeValueChangeListener(modelListener);
+ }
+ }
+
+ /**
+ * 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;
+
+ IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+ public void handleValueChange(ValueChangeEvent<V> event) {
+ targetBinding.setTargetValue(event.diff.getNewValue());
+ }
+ };
+
+ public OneWayModelBinding(IObservableValue<V> modelObservable) {
+ this.modelObservable = modelObservable;
+
+ modelObservable.addValueChangeListener(modelListener);
+ }
+
+ public V getModelValue() {
+ return modelObservable.getValue();
+ }
+
+ public void removeModelListener() {
+ // Actually we don't own this observable so don't dispose,
+ // just remove our listener.
+ modelObservable.removeValueChangeListener(modelListener);
+ }
+ }
+
+ /**
+ * @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);
+ }
+
+ /**
+ * @param modelObservable
+ * @return an object that can chain two-way bindings
+ */
+ public static <V> ITwoWayBinding<V> twoWay(
+ final IObservableValue<V> modelObservable) {
+ 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/databinding/bind/DefaultValueBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java
new file mode 100644
index 00000000..f5dacf44
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java
@@ -0,0 +1,76 @@
+package org.eclipse.core.databinding.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/databinding/bind/IBidiConverter.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java
new file mode 100644
index 00000000..183ca3b3
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java
@@ -0,0 +1,27 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @since 1.5
+ *
+ * @param <T1>
+ * @param <T2>
+ */
+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/databinding/bind/IModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java
new file mode 100644
index 00000000..0f03e748
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java
@@ -0,0 +1,25 @@
+package org.eclipse.core.databinding.bind;
+
+/**
+ * @since 1.5
+ *
+ * @param <T>
+ */
+public 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/databinding/bind/IOneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java
new file mode 100644
index 00000000..eeac0571
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java
@@ -0,0 +1,62 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+/**
+ * @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);
+
+ /**
+ * @param targetObservable
+ */
+ void to(IObservableValue<T1> targetObservable);
+
+ /**
+ * 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
+ */
+ ITwoWayBinding<T1> untilTargetChanges();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java
new file mode 100644
index 00000000..98be6c79
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java
@@ -0,0 +1,20 @@
+package org.eclipse.core.databinding.bind;
+
+/**
+ * @since 1.5
+ *
+ * @param <T>
+ */
+public 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/databinding/bind/ITargetBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java
new file mode 100644
index 00000000..3fc6d6cb
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java
@@ -0,0 +1,22 @@
+package org.eclipse.core.databinding.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/databinding/bind/ITwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java
new file mode 100644
index 00000000..124066eb
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java
@@ -0,0 +1,43 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+/**
+ * @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);
+
+ /**
+ * @param targetObservable
+ */
+ void to(IObservableValue<T1> targetObservable);
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java
new file mode 100644
index 00000000..8c82d1a1
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java
@@ -0,0 +1,149 @@
+package org.eclipse.core.databinding.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.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 binding where
+ * <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();
+ }
+ });
+ }
+
+ /**
+ * @param status
+ */
+ public void setStatus(IStatus status) {
+ targetBinding.setStatus(status);
+ }
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java
new file mode 100644
index 00000000..f5fe2019
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java
@@ -0,0 +1,43 @@
+package org.eclipse.core.databinding.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/databinding/bind/TwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java
new file mode 100644
index 00000000..f7860ac5
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java
@@ -0,0 +1,167 @@
+package org.eclipse.core.databinding.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.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
+ */
+ 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);
+ }
+
+ /**
+ * 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/databinding/bind/TwoWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java
new file mode 100644
index 00000000..e9856920
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java
@@ -0,0 +1,71 @@
+package org.eclipse.core.databinding.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/databinding/bind/TwoWayValidationBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java
new file mode 100644
index 00000000..a7821109
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java
@@ -0,0 +1,80 @@
+package org.eclipse.core.databinding.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