blob: 4577664e09f96863f4836786d618b65f3710af34 [file] [log] [blame]
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();
}
});
}
}