| /******************************************************************************* |
| * 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} < {@link java.util.Date} > 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} < {@link java.util.Date} |
| * >) and the hour, minute, second, and millisecond portion of the time |
| * observable (also an {@link IObservableValue} < {@link java.util.Date} |
| * >). |
| * <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(); |
| } |
| } |