blob: 8e20a486224b431e351b993ebcaf94fb6758491a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Matthew Hall and others.
* 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:
* Matthew Hall - initial API and implementation (bug 169876)
* Elias Volanakis <elias@eclipsesource.com> - bug 271720
******************************************************************************/
package org.eclipse.core.databinding.observable.value;
import java.util.Calendar;
import java.util.Date;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.DisposeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IDisposeListener;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.internal.databinding.observable.Util;
import org.eclipse.core.runtime.Assert;
/**
* An {@link IObservableValue} &lt; {@link java.util.Date} &gt; which supports
* scenarios where the date and time are presented as separate elements in the
* user interface. This class combines the year, month, and day portion of the
* date observable (an {@link IObservableValue} &lt; {@link java.util.Date}
* &gt;) and the hour, minute, second, and millisecond portion of the time
* observable (also an {@link IObservableValue} &lt; {@link java.util.Date}
* &gt;).
* <p>
* This observable's value will be null whenever the date observable's value is
* null. Otherwise the value is the combination of the date portion of the date
* observable and the time portion of the time observable (a time observable
* value of null is treated the same as 0:00:00.000).
* <p>
* When setting the value of this observable, setting a null value will set null
* on the date observable, and set a time of 0:00:00.000 on the time observable.
* When setting non-null values, the non-applicable fields of each observable
* are left intact. That is, the hour, minute, second and millisecond components
* of the date observable are preserved, and the year, month and day components
* of the time observable are preserved.
* <p>
* The observables used for the date and time component may impose their own
* restrictions with regard to supported values. For example some observables do
* not allow a null value, because the underlying widget lacks support for a
* null value (example: DateTime).
* <p>
* One use for this class is binding a date-and-time value to two separate user
* interface elements, one for editing date and one for editing time:
*
* <pre>
* DataBindingContext dbc = new DataBindingContext();
* IObservableValue beanValue = BeansObservables.observeValue(...);
* IObservableValue dateObservable = WidgetProperties.selection().observe(
* dateWidget);
* IObservableValue timeObservable = WidgetProperties.selection().observe(
* timeWidget);
* dbc.bindValue(new DateAndTimeObservableValue(dateObservable, timeObservable),
* beanValue);
* </pre>
*
* A second use is editing only the date or time value of a date-and-time value.
* This can be accomplished by using a widget-specific observable for the
* editable value and a WritableValue as a container for the fixed value. The
* example below allows editing the date while preserving the time:
*
* <pre>
* DataBindingContext dbc = new DataBindingContext();
* IObservableValue beanValue = BeansObservables.observeValue(...);
* IObservableValue dateObservable = WidgetProperties.selection().observe(
* dateWidget);
* IObservableValue timeObservable = new WritableValue(dateObservable.getRealm(),
* beanValue.getValue(), Date.class);
* dbc.bindValue(new DateAndTimeObservableValue(dateObservable, timeObservable), beanValue);
*
* <pre>
*
* @since 1.2
*/
public class DateAndTimeObservableValue extends AbstractObservableValue<Date> {
private IObservableValue<Date> dateObservable;
private IObservableValue<Date> timeObservable;
private PrivateChangeInterface privateChangeInterface;
private PrivateStaleInterface privateStaleInterface;
private PrivateDisposeInterface privateDisposeInterface;
private Date cachedValue;
private boolean updating;
private class PrivateChangeInterface implements IChangeListener {
public void handleChange(ChangeEvent event) {
if (!isDisposed() && !updating)
notifyIfChanged();
}
}
private class PrivateStaleInterface implements IStaleListener {
public void handleStale(StaleEvent staleEvent) {
if (!isDisposed())
fireStale();
}
}
private class PrivateDisposeInterface implements IDisposeListener {
public void handleDispose(DisposeEvent staleEvent) {
dispose();
}
}
// One calendar per thread to preserve thread-safety
private static final ThreadLocal<Calendar> calendar = new ThreadLocal<Calendar>() {
protected Calendar initialValue() {
return Calendar.getInstance();
}
};
/**
* Constructs a DateAndTimeObservableValue with the specified constituent
* observables.
*
* @param dateObservable
* the observable used for the date component (year, month and
* day) of the constructed observable.
* @param timeObservable
* the observable used for the time component (hour, minute,
* second and millisecond) of the constructed observable.
*/
public DateAndTimeObservableValue(IObservableValue<Date> dateObservable,
IObservableValue<Date> timeObservable) {
super(dateObservable.getRealm());
this.dateObservable = dateObservable;
this.timeObservable = timeObservable;
Assert.isTrue(dateObservable.getRealm().equals(
timeObservable.getRealm()));
privateChangeInterface = new PrivateChangeInterface();
privateStaleInterface = new PrivateStaleInterface();
privateDisposeInterface = new PrivateDisposeInterface();
dateObservable.addDisposeListener(privateDisposeInterface);
timeObservable.addDisposeListener(privateDisposeInterface);
}
public Object getValueType() {
return Date.class;
}
protected void firstListenerAdded() {
cachedValue = doGetValue();
dateObservable.addChangeListener(privateChangeInterface);
dateObservable.addStaleListener(privateStaleInterface);
timeObservable.addChangeListener(privateChangeInterface);
timeObservable.addStaleListener(privateStaleInterface);
}
protected void lastListenerRemoved() {
if (dateObservable != null && !dateObservable.isDisposed()) {
dateObservable.removeChangeListener(privateChangeInterface);
dateObservable.removeStaleListener(privateStaleInterface);
}
if (timeObservable != null && !timeObservable.isDisposed()) {
timeObservable.removeChangeListener(privateChangeInterface);
timeObservable.removeStaleListener(privateStaleInterface);
}
cachedValue = null;
}
private void notifyIfChanged() {
if (hasListeners()) {
Date oldValue = cachedValue;
Date newValue = cachedValue = doGetValue();
if (!Util.equals(oldValue, newValue))
fireValueChange(Diffs.createValueDiff(oldValue, newValue));
}
}
/**
* @since 1.5
*/
protected Date doGetValue() {
Date dateValue = dateObservable.getValue();
if (dateValue == null)
return null;
Date timeValue = timeObservable.getValue();
Calendar cal = calendar.get();
cal.setTime(dateValue);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
if (timeValue == null)
cal.clear();
else
cal.setTime(timeValue);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
int millis = cal.get(Calendar.MILLISECOND);
cal.set(year, month, day, hour, minute, second);
cal.set(Calendar.MILLISECOND, millis);
return cal.getTime();
}
/**
* @since 1.5
*/
protected void doSetValue(Date combinedDate) {
Date dateValue;
Date timeValue;
Calendar cal = calendar.get();
if (combinedDate == null)
cal.clear();
else
cal.setTime(combinedDate);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
int millis = cal.get(Calendar.MILLISECOND);
if (combinedDate == null) {
dateValue = null;
} else {
dateValue = dateObservable.getValue();
if (dateValue == null)
cal.clear();
else
cal.setTime(dateValue);
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, day);
dateValue = cal.getTime();
}
timeValue = timeObservable.getValue();
if (timeValue == null)
cal.clear();
else
cal.setTime(timeValue);
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, second);
cal.set(Calendar.MILLISECOND, millis);
timeValue = cal.getTime();
updating = true;
try {
dateObservable.setValue(dateValue);
timeObservable.setValue(timeValue);
} finally {
updating = false;
}
notifyIfChanged();
}
public boolean isStale() {
ObservableTracker.getterCalled(this);
return dateObservable.isStale() || timeObservable.isStale();
}
public synchronized void dispose() {
checkRealm();
if (!isDisposed()) {
if (!dateObservable.isDisposed()) {
dateObservable.removeDisposeListener(privateDisposeInterface);
dateObservable.removeChangeListener(privateChangeInterface);
dateObservable.removeStaleListener(privateStaleInterface);
}
if (!timeObservable.isDisposed()) {
timeObservable.removeDisposeListener(privateDisposeInterface);
timeObservable.removeChangeListener(privateChangeInterface);
timeObservable.removeStaleListener(privateStaleInterface);
}
dateObservable = null;
timeObservable = null;
privateChangeInterface = null;
privateStaleInterface = null;
privateDisposeInterface = null;
cachedValue = null;
}
super.dispose();
}
}