blob: bab30137381b8f178af74839a0e7afadaf96090b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 The Pampered Chef 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:
* The Pampered Chef - initial API and implementation
******************************************************************************/
package org.eclipse.jface.examples.databinding.compositetable.day.binding;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.jface.databinding.observable.AbstractObservable;
import org.eclipse.jface.examples.databinding.compositetable.day.CalendarableItemEvent;
import org.eclipse.jface.examples.databinding.compositetable.day.CalendarableItemEventHandler;
import org.eclipse.jface.examples.databinding.compositetable.day.CalendarableSelectionChangeListener;
import org.eclipse.jface.examples.databinding.compositetable.day.NewEvent;
import org.eclipse.jface.examples.databinding.compositetable.day.SelectionChangeEvent;
import org.eclipse.jface.examples.databinding.compositetable.day.internal.ICalendarableItemControl;
import org.eclipse.jface.examples.databinding.compositetable.reflect.ReflectedProperty;
import org.eclipse.jface.examples.databinding.compositetable.timeeditor.CalendarableItem;
import org.eclipse.jface.examples.databinding.compositetable.timeeditor.EventContentProvider;
import org.eclipse.jface.examples.databinding.compositetable.timeeditor.EventCountProvider;
import org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor;
import org.eclipse.jface.internal.databinding.provisional.BindSpec;
import org.eclipse.jface.internal.databinding.provisional.Binding;
import org.eclipse.jface.internal.databinding.provisional.DataBindingContext;
import org.eclipse.jface.internal.databinding.provisional.description.Property;
import org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor;
import org.eclipse.jface.internal.databinding.provisional.observable.ILazyListElementProvider;
import org.eclipse.jface.internal.databinding.provisional.observable.LazyDeleteEvent;
import org.eclipse.jface.internal.databinding.provisional.observable.LazyInsertDeleteProvider;
import org.eclipse.jface.internal.databinding.provisional.observable.LazyInsertEvent;
import org.eclipse.swt.SWT;
/**
* An Observable for IEventEditor objects.
*
* @since 3.2
*/
public class EventEditorObservableLazyDataRequestor extends AbstractObservable implements ILazyDataRequestor {
private IEventEditor editor;
private MultiDayEventCalendar multiDayEventCalendar = new MultiDayEventCalendar();
protected int modelSize = 0;
private DataBindingContext dbc;
private String startTimePropertyName; // Required; rest are optional
private String endTimePropertyName = null;
private String textPropertyName = null;
private String toolTipTextPropertyName = null;
private String imagePropertyName = null;
private String allDayEventPropertyName = null;
private class EventToCalendarableItem {
/**
* a in the pair (a, b)
*/
public Object event;
/**
* b in the pair (a, b)
*/
public CalendarableItem item;
/**
* Construct a Pair(a, b)
*
* @param a a in the pair (a, b)
* @param b b in the pair (a, b)
*/
public EventToCalendarableItem(Object a, CalendarableItem b) {
this.event = a;
this.item = b;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj.getClass() != EventToCalendarableItem.class) {
return false;
}
EventToCalendarableItem other = (EventToCalendarableItem) obj;
return event.equals(other.event) && item.equals(other.item);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return event.hashCode() + item.hashCode();
}
}
private class MultiDayEventCalendar {
private Map daysToEventsMap;
private List eventsList;
public MultiDayEventCalendar() {
flush();
}
public void flush() {
daysToEventsMap = new TreeMap();
eventsList = new LinkedList();
for (int i=0; i < modelSize; ++i) {
Object event = getModelElementAt(i);
add(event);
}
}
public void add(Object event) {
eventsList.add(event);
addMultiDayEventToMap(event);
}
private void addMultiDayEventToMap(Object event) {
Date beginningDate = getBeginningDate(event);
Date endingDate = getEndingDate(event);
for (Date currentDate = beginningDate;
currentDate.before(endingDate);
currentDate = nextDay(currentDate))
{
addEventToMap(currentDate, event);
}
}
private void addEventToMap(Object date, Object event) {
List events = (List) daysToEventsMap.get(date);
if (events == null) {
events = new LinkedList();
daysToEventsMap.put(date, events);
}
EventToCalendarableItem eventToCalenderable = new EventToCalendarableItem(event, null);
events.add(eventToCalenderable);
daysToEventsMap.put(date, events);
}
public Object remove(int position) {
Object toRemove = eventsList.remove(position);
Date beginningDate = getBeginningDate(toRemove);
Date endingDate = getEndingDate(toRemove);
for (Date currentDate = beginningDate;
currentDate.before(endingDate);
currentDate = nextDay(currentDate))
{
removeEventFromMap(currentDate, toRemove);
}
return toRemove;
}
private void removeEventFromMap(Date date, Object event) {
List events = (List) daysToEventsMap.get(date);
if (events == null) {
// TODO: Log warning here?
return;
}
for (Iterator eventsIter = events.iterator(); eventsIter.hasNext();) {
EventToCalendarableItem eventToCalendarable = (EventToCalendarableItem) eventsIter.next();
if (eventToCalendarable.event.equals(event)) {
eventsIter.remove();
break;
}
}
events.remove(event);
if (events.size() < 1) {
daysToEventsMap.remove(date);
}
}
public void update(EventDateTimeDiff diff, Object event) {
// Remove the event in the old position
Date oldStartDateTime = setToStartOfDay(diff.getOldStartDateTime());
Date oldEndDateTime = incrementDay(setToStartOfDay(diff.getOldEndDateTime()), 1);
for (Date currentDate = oldStartDateTime;
currentDate.before(oldEndDateTime);
currentDate = nextDay(currentDate))
{
removeEventFromMap(currentDate, event);
}
addMultiDayEventToMap(event);
// add(event); Can't do this because it adds dupes to the eventsList
}
private List get(Date date) {
date = setToStartOfDay(date);
return (List) daysToEventsMap.get(date);
}
public int indexOf(Object event) {
return eventsList.indexOf(event);
}
public void setCalendarableSelection(Object event, boolean selected) {
Date beginningDate = getBeginningDate(event);
Date endingDate = getEndingDate(event);
for (Date currentDate = beginningDate;
currentDate.before(endingDate);
currentDate = nextDay(currentDate))
{
List events = (List) daysToEventsMap.get(currentDate);
if (events == null) { // If we just deleted this event, return
return;
}
for (Iterator eventsIter = events.iterator(); eventsIter.hasNext();) {
EventToCalendarableItem eventToCalendarable = (EventToCalendarableItem) eventsIter.next();
if (eventToCalendarable.event.equals(event)) {
if (eventToCalendarable.item != null) {
ICalendarableItemControl control = eventToCalendarable.item.getControl();
if (control != null) {
control.setSelected(selected);
}
}
break;
}
}
}
}
public int getNumberOfEventsInDay(Date day) {
List dataForDate = multiDayEventCalendar.get(day);
if (dataForDate == null) {
return 0;
}
return dataForDate.size();
}
public void refresh(Date day, CalendarableItem[] items) {
List dataForDate = multiDayEventCalendar.get(day);
if (dataForDate == null) {
return;
}
Iterator sourceEventIter = dataForDate.iterator();
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
EventToCalendarableItem sourceEventPair = (EventToCalendarableItem) sourceEventIter.next();
sourceEventPair.item = items[itemIndex];
Object sourceEvent = sourceEventPair.event;
Date startDate = getBeginningDate(sourceEvent);
Date endDate = getEndingTime(sourceEvent);
int dayWithinEvent = differenceInDays(day, startDate);
int numberOfDaysInEvent = differenceInDays(endDate, startDate)+1;
bindCalendarableItemProperties(items[itemIndex], sourceEvent, dayWithinEvent, numberOfDaysInEvent);
}
}
/**
* Returns 11:59:999 PM of the ending date of the passed event.
*
* @param it The event
* @return The ending of the last day
*/
protected Date getEndingDate(Object it) {
ReflectedProperty property = new ReflectedProperty(it, endTimePropertyName);
Date endingDate = (Date) property.get();
Date endOfEndingDate = setToEndOfDay(endingDate);
return endOfEndingDate;
}
private int differenceInDays(Date date, Date daysToSubtract) {
long difference = date.getTime() - daysToSubtract.getTime();
difference /= (1000*60*60*24);
return (int) difference;
}
}
/**
* @param d
*/
public EventEditorObservableLazyDataRequestor(EventEditorBindingDescription d) {
super();
this.editor = d.editor;
this.dbc = d.dbc;
if (d.startTimePropertyName == null) {
throw new IllegalArgumentException("Start time property description cannot be null");
}
this.startTimePropertyName = d.startTimePropertyName;
this.endTimePropertyName = d.endTimePropertyName;
this.allDayEventPropertyName = d.allDayEventPropertyName;
this.textPropertyName = d.textPropertyName;
this.toolTipTextPropertyName = d.toolTipTextPropertyName;
this.imagePropertyName = d.imagePropertyName;
editor.setEventCountProvider(eventCountProvider);
editor.setEventContentProvider(eventContentProvider);
editor.addItemDisposeHandler(itemDisposeHandler);
editor.addItemInsertHandler(insertHandler);
editor.addItemDeleteHandler(deleteHandler);
editor.addItemEditHandler(editHandler);
editor.addSelectionChangeListener(selectionListener);
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.AbstractObservable#dispose()
*/
public void dispose() {
super.dispose();
if (editor == null) {
return;
}
editor.removeItemInsertHandler(insertHandler);
editor.removeItemDeleteHandler(deleteHandler);
editor.removeItemDisposeHandler(itemDisposeHandler);
editor.removeItemEditHandler(editHandler);
editor.setEventCountProvider(null);
editor.setEventContentProvider(null);
editor = null; // encourage the garbage collector to run... ;-)
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.IObservable#isStale()
*/
public boolean isStale() {
return false;
}
private List elementProviders = new ArrayList();
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#addElementProvider(org.eclipse.jface.internal.databinding.provisional.observable.ILazyListElementProvider)
*/
public void addElementProvider(ILazyListElementProvider p) {
elementProviders.add(p);
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#removeElementProvider(org.eclipse.jface.internal.databinding.provisional.observable.ILazyListElementProvider)
*/
public void removeElementProvider(ILazyListElementProvider p) {
elementProviders.remove(p);
}
private Object getModelElementAt(int index) {
for (Iterator epIter = elementProviders.iterator(); epIter.hasNext();) {
ILazyListElementProvider p = (ILazyListElementProvider) epIter.next();
Object result = p.get(index);
if (result != null) {
return result;
}
}
throw new IndexOutOfBoundsException("Request for a nonexistent element");
}
private List insertDeleteProviders = new ArrayList();
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#addInsertDeleteProvider(org.eclipse.jface.internal.databinding.provisional.observable.LazyInsertDeleteProvider)
*/
public void addInsertDeleteProvider(LazyInsertDeleteProvider p) {
insertDeleteProviders.add(p);
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#removeInsertDeleteProvider(org.eclipse.jface.internal.databinding.provisional.observable.LazyInsertDeleteProvider)
*/
public void removeInsertDeleteProvider(LazyInsertDeleteProvider p) {
insertDeleteProviders.remove(p);
}
private NewObject fireInsert(CalendarableItem initializationData) {
for (Iterator iter = insertDeleteProviders.iterator(); iter.hasNext();) {
LazyInsertDeleteProvider p = (LazyInsertDeleteProvider) iter.next();
NewObject result = p.insertElementAt(new LazyInsertEvent(0, initializationData));
if (result != null) {
return result;
}
}
return null;
}
private boolean fireDelete(int position) {
for (Iterator iter = insertDeleteProviders.iterator(); iter.hasNext();) {
LazyInsertDeleteProvider p = (LazyInsertDeleteProvider) iter.next();
LazyDeleteEvent e = new LazyDeleteEvent(position);
boolean result = p.canDeleteElementAt(e);
if (result) {
p.deleteElementAt(e);
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#setSize(int)
*/
public void setSize(int size) {
this.modelSize = size;
multiDayEventCalendar.flush();
editor.refresh();
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#add(int, java.lang.Object)
*/
public void add(int position, Object element) {
multiDayEventCalendar.add(element);
modelSize++;
editor.refresh();
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.ILazyDataRequestor#remove(int)
*/
public void remove(int position) {
multiDayEventCalendar.remove(position);
modelSize--;
editor.refresh();
}
// Utility methods here ---------------------------------------------------
protected Date incrementDay(Date initialDate, int increment) {
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(initialDate);
gc.add(Calendar.DATE, increment);
return gc.getTime();
}
protected Date nextDay(Date initialDate) {
return incrementDay(initialDate, 1);
}
protected static Date setToStartOfDay(Date rawDate) {
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(rawDate);
gc.set(Calendar.HOUR_OF_DAY, 0);
gc.set(Calendar.MINUTE, 0);
gc.set(Calendar.SECOND, 0);
gc.set(Calendar.MILLISECOND, 0);
return gc.getTime();
}
/**
* Returns 12 AM of the beginning date of the passed event.
*
* @param it The event
* @return The beginning of the start day.
*/
protected Date getBeginningDate(Object it) {
ReflectedProperty property = new ReflectedProperty(it, startTimePropertyName);
Date date = setToStartOfDay((Date) property.get());
return date;
}
protected static Date setToEndOfDay(Date date) {
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(date);
gc.set(Calendar.HOUR_OF_DAY, 23);
gc.set(Calendar.MINUTE, 59);
gc.set(Calendar.SECOND, 59);
gc.set(Calendar.MILLISECOND, 999);
Date time = gc.getTime();
return time;
}
private Date getBeginningTime(Object it) {
ReflectedProperty property = new ReflectedProperty(it, startTimePropertyName);
return ((Date) property.get());
}
private Date getEndingTime(Object it) {
if (endTimePropertyName == null) {
return getBeginningTime(it);
}
ReflectedProperty property = new ReflectedProperty(it, endTimePropertyName);
return (Date)property.get();
}
// Event handlers here ----------------------------------------------------
protected void bindCalendarableItemProperties(
CalendarableItem item,
Object sourceElement,
int eventPosition,
int eventLength) {
// Optional bindings first...
if (allDayEventPropertyName != null) {
bindCalendarableItem(
item, CalendarableItem.PROP_ALL_DAY_EVENT,
sourceElement, allDayEventPropertyName, null);
}
if (textPropertyName != null) {
bindCalendarableItem(
item, CalendarableItem.PROP_TEXT,
sourceElement, textPropertyName, null);
}
if (toolTipTextPropertyName != null) {
bindCalendarableItem(
item, CalendarableItem.PROP_TOOL_TIP_TEXT,
sourceElement, toolTipTextPropertyName, null);
}
if (imagePropertyName != null) {
bindCalendarableItem(
item, CalendarableItem.PROP_IMAGE,
sourceElement, imagePropertyName, null);
}
// Now the standard bindings...
item.setContinued(SWT.NULL);
if (eventLength == 1) {
item.setDate(getBeginningDate(sourceElement));
item.setStartTime(getBeginningTime(sourceElement));
item.setEndTime(getEndingTime(sourceElement));
} else { // multiday event
if (eventPosition == 0) { // first day of event
Date day = getBeginningDate(sourceElement);
item.setDate(day);
item.setStartTime(getBeginningTime(sourceElement));
item.setEndTime(setToEndOfDay(day));
if (!item.isAllDayEvent())
item.setContinued(SWT.BOTTOM);
} else if (eventPosition == eventLength - 1) { // last day of event
Date beginningOfEndDay = setToStartOfDay(getEndingTime(sourceElement));
item.setDate(beginningOfEndDay);
item.setStartTime(beginningOfEndDay);
item.setEndTime(getEndingTime(sourceElement));
if (!item.isAllDayEvent())
item.setContinued(SWT.TOP);
} else { // in between first and last day of event
Date day = incrementDay(getBeginningDate(sourceElement), eventPosition);
Date startOfDay = setToStartOfDay(day);
item.setDate(startOfDay);
item.setStartTime(startOfDay);
item.setEndTime(setToEndOfDay(day));
if (!item.isAllDayEvent())
item.setContinued(SWT.TOP | SWT.BOTTOM);
}
}
item.setData(CalendarableItem.DATA_KEY, sourceElement);
}
private void bindCalendarableItem(CalendarableItem item, String itemPropertyName, Object sourceElement, String sourcePropertyName, BindSpec bindSpec) {
Binding binding = dbc.bind(new Property(item, itemPropertyName),
new Property(sourceElement, sourcePropertyName), bindSpec);
List bindingList = (List) item.getData(CalendarableItem.BINDING_KEY);
if (bindingList == null) {
bindingList = new ArrayList();
item.setData(CalendarableItem.BINDING_KEY, bindingList);
}
bindingList.add(binding);
}
private CalendarableItemEventHandler itemDisposeHandler = new CalendarableItemEventHandler() {
public void handleRequest(CalendarableItemEvent e) {
List bindings = (List)e.calendarableItem.getData(CalendarableItem.BINDING_KEY);
if (bindings != null) {
for (Iterator bindingIter = bindings.iterator(); bindingIter.hasNext();) {
Binding binding = (Binding) bindingIter.next();
binding.dispose();
}
}
}
};
private EventCountProvider eventCountProvider = new EventCountProvider() {
public int getNumberOfEventsInDay(Date day) {
return multiDayEventCalendar.getNumberOfEventsInDay(day);
}
};
private EventContentProvider eventContentProvider = new EventContentProvider() {
public void refresh(Date day, CalendarableItem[] items) {
multiDayEventCalendar.refresh(day, items);
}
};
private CalendarableItemEventHandler insertHandler = new CalendarableItemEventHandler() {
public void handleRequest(CalendarableItemEvent e) {
NewObject newObject = fireInsert(e.calendarableItem);
if (newObject == null) {
e.doit = false;
return;
}
multiDayEventCalendar.add(newObject.it);
Date firstDayOfEvent = getBeginningTime(newObject.it);
Date lastDayOfEvent = getEndingTime(newObject.it);
e.result = new NewEvent(newObject.it, new Date[] {firstDayOfEvent, lastDayOfEvent});
}
};
private CalendarableItemEventHandler deleteHandler = new CalendarableItemEventHandler() {
public void handleRequest(CalendarableItemEvent e) {
int objectToDelete = multiDayEventCalendar.indexOf(e.calendarableItem.getData(CalendarableItem.DATA_KEY));
if (!fireDelete(objectToDelete)) {
e.doit = false;
return;
}
multiDayEventCalendar.remove(objectToDelete);
}
};
private CalendarableItemEventHandler editHandler = new CalendarableItemEventHandler() {
public void requestHandled(CalendarableItemEvent e) {
if (e.result != null && e.doit) {
multiDayEventCalendar.update((EventDateTimeDiff) e.result,
e.calendarableItem.getData(CalendarableItem.DATA_KEY));
}
}
};
private CalendarableSelectionChangeListener selectionListener = new CalendarableSelectionChangeListener() {
public void selectionChanged(SelectionChangeEvent e) {
if (e.oldSelection != null) {
multiDayEventCalendar.setCalendarableSelection(e.oldSelection.getData(CalendarableItem.DATA_KEY), false);
}
if (e.newSelection != null) {
multiDayEventCalendar.setCalendarableSelection(e.newSelection.getData(CalendarableItem.DATA_KEY), true);
}
}
};
}