/*******************************************************************************
* 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.swt.nebula.widgets.compositetable.day;
import java.text.SimpleDateFormat;
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 org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.nebula.widgets.compositetable.CompositeTable;
import org.eclipse.swt.nebula.widgets.compositetable.IRowContentProvider;
import org.eclipse.swt.nebula.widgets.compositetable.RowConstructionListener;
import org.eclipse.swt.nebula.widgets.compositetable.ScrollEvent;
import org.eclipse.swt.nebula.widgets.compositetable.ScrollListener;
import org.eclipse.swt.nebula.widgets.compositetable.day.internal.DayEditorCalendarableItemControl;
import org.eclipse.swt.nebula.widgets.compositetable.day.internal.EventLayoutComputer;
import org.eclipse.swt.nebula.widgets.compositetable.day.internal.TimeSlice;
import org.eclipse.swt.nebula.widgets.compositetable.day.internal.TimeSlot;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.AbstractEventEditor;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.CalendarableItem;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.CalendarableModel;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.EventContentProvider;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.EventCountProvider;
import org.eclipse.swt.nebula.widgets.compositetable.timeeditor.IEventEditor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
/**
* A DayEditor is an SWT control that can display events on a time line that can
* span one or more days. This class is not intended to be subclassed.
*
* @since 3.2
*/
public class DayEditor extends AbstractEventEditor implements IEventEditor {
private CompositeTable compositeTable = null;
private CalendarableModel model = new CalendarableModel();
private List recycledCalendarableEventControls = new LinkedList();
protected TimeSlice daysHeader = null;
private final boolean headerDisabled;
/**
* NO_HEADER constant. A style bit constant to indicate that no header
* should be displayed at the top of the editor window.
*/
public static final int NO_HEADER=SWT.NO_TRIM;
/**
* Constructor DayEditor. Constructs a calendar control that can display
* events on one or more days.
*
* @param parent
* @param style DayEditor.NO_HEADER or SWT.NO_TRIM means not to display a header.
*/
public DayEditor(Composite parent, int style) {
super(parent, SWT.NULL);
if ((style & NO_HEADER) != 0) {
headerDisabled = true;
} else {
headerDisabled = false;
}
setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#setTimeBreakdown(int, int)
*/
public void setTimeBreakdown(int numberOfDays, int numberOfDivisionsInHour) {
checkWidget();
model.setTimeBreakdown(numberOfDays, numberOfDivisionsInHour);
if (compositeTable != null) {
compositeTable.dispose();
}
createCompositeTable(numberOfDays, numberOfDivisionsInHour);
}
/**
* This method initializes compositeTable
*
* @param numberOfDays
* The number of day columns to display
*/
private void createCompositeTable(final int numberOfDays,
final int numberOfDivisionsInHour) {
compositeTable = new CompositeTable(this, SWT.NONE);
if (background != null) {
compositeTable.setBackground(background);
}
compositeTable.setTraverseOnTabsEnabled(false);
if (!headerDisabled) {
new TimeSlice(compositeTable, SWT.BORDER); // The prototype header
}
new TimeSlice(compositeTable, SWT.NONE); // The prototype row
compositeTable.setNumRowsInCollection(computeNumRowsInCollection(numberOfDivisionsInHour));
compositeTable.addRowConstructionListener(new RowConstructionListener() {
public void headerConstructed(Control newHeader) {
daysHeader = (TimeSlice) newHeader;
daysHeader.setHeaderControl(true);
daysHeader.setNumberOfColumns(numberOfDays);
if (model.getStartDate() == null) {
return;
}
refreshColumnHeaders(daysHeader.getColumns());
}
public void rowConstructed(Control newRow) {
TimeSlice timeSlice = (TimeSlice) newRow;
timeSlice.setNumberOfColumns(numberOfDays);
timeSlice.addCellFocusListener(cellFocusListener);
timeSlice.addKeyListener(keyListener);
timeSlice.addMouseListener(cellMouseListener);
}
});
compositeTable.addRowContentProvider(new IRowContentProvider() {
public void refresh(CompositeTable sender, int currentObjectOffset,
Control row) {
TimeSlice timeSlice = (TimeSlice) row;
refreshRow(currentObjectOffset, timeSlice);
}
});
compositeTable.addScrollListener(new ScrollListener() {
public void tableScrolled(ScrollEvent scrollEvent) {
layoutEventControls();
}
});
addControlListener(new ControlAdapter() {
public void controlResized(ControlEvent e) {
Rectangle bounds = DayEditor.this.getBounds();
compositeTable.setBounds(0, 0, bounds.width, bounds.height);
layoutEventControlsDeferred();
}
});
compositeTable.setRunTime(true);
}
private Menu menu = null;
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#setMenu(org.eclipse.swt.widgets.Menu)
*/
public void setMenu(final Menu menu) {
checkWidget();
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
if (isDisposed()) return;
DayEditor.super.setMenu(menu);
DayEditor.this.menu = menu;
compositeTable.setMenu(menu);
setMenuOnCollection(recycledCalendarableEventControls, menu);
for (int day=0; day < model.getNumberOfDays(); ++day) {
List calendarablesForDay = model.getCalendarableItems(day);
setMenuOnCollection(calendarablesForDay, menu);
}
}
});
}
private void setMenuOnCollection(List collection, Menu menu) {
for (Iterator controls = collection.iterator(); controls.hasNext();) {
ICalendarableItemControl control = (ICalendarableItemControl) controls.next();
control.setMenu(menu);
}
}
private ArrayList keyListeners = new ArrayList();
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#addKeyListener(org.eclipse.swt.events.KeyListener)
*/
public void addKeyListener(KeyListener listener) {
checkWidget();
keyListeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#removeKeyListener(org.eclipse.swt.events.KeyListener)
*/
public void removeKeyListener(KeyListener listener) {
checkWidget();
keyListeners.remove(listener);
}
private KeyListener keyListener = new KeyAdapter() {
public void keyReleased(KeyEvent e) {
for (Iterator i = keyListeners.iterator(); i.hasNext();) {
KeyListener keyListener = (KeyListener) i.next();
keyListener.keyReleased(e);
if (!e.doit) return;
}
}
public void keyPressed(KeyEvent e) {
for (Iterator i = keyListeners.iterator(); i.hasNext();) {
KeyListener keyListener = (KeyListener) i.next();
keyListener.keyPressed(e);
if (!e.doit) return;
}
CalendarableItem selection = selectedCalendarable;
int selectedRow;
int selectedDay;
boolean allDayEventRowSelected = false;
int compositeTableRow = compositeTable.getSelection().y + compositeTable.getTopRow();
if (compositeTableRow < numberOfAllDayEventRows) {
allDayEventRowSelected = true;
}
if (selection == null) {
selectedRow = convertViewportRowToDayRow(compositeTable.getCurrentRow());
selectedDay = compositeTable.getCurrentColumn();
} else {
selectedDay = model.getDay(selection);
if (allDayEventRowSelected) {
selectedRow = compositeTableRow;
} else {
Point selectedCoordinates = selection.getUpperLeftPositionInDayRowCoordinates();
if (selectedCoordinates == null) {
return;
}
selectedRow = selectedCoordinates.y;
}
}
switch (e.character) {
case SWT.TAB:
if ((e.stateMask & SWT.SHIFT) != 0) {
CalendarableItem newSelection = model.findPreviousCalendarable(selectedDay, selectedRow, selection, allDayEventRowSelected);
if (newSelection == null) {
// There was only 0 or one visible event--nothing to scroll to
return;
}
int newTopRow = computeNewTopRowBasedOnSelection(newSelection);
if (newTopRow != compositeTable.getTopRow()) {
compositeTable.setTopRow(newTopRow);
}
setSelection(newSelection);
} else {
CalendarableItem newSelection = model.findNextCalendarable(selectedDay, selectedRow, selection, allDayEventRowSelected);
if (newSelection == null) {
// There was only 0 or one visible event--nothing to scroll to
return;
}
int newTopRow = computeNewTopRowBasedOnSelection(newSelection);
if (newTopRow != compositeTable.getTopRow()) {
compositeTable.setTopRow(newTopRow);
}
setSelection(newSelection);
}
}
}
};
private ArrayList mouseListeners = new ArrayList();
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#addMouseListener(org.eclipse.swt.events.MouseListener)
*/
public void addMouseListener(MouseListener listener) {
checkWidget();
mouseListeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#removeMouseListener(org.eclipse.swt.events.MouseListener)
*/
public void removeMouseListener(MouseListener listener) {
checkWidget();
mouseListeners.remove(listener);
}
private MouseListener cellMouseListener = new MouseListener() {
public void mouseDoubleClick(MouseEvent e) {
fireMouseDoubleClickEvent(e);
}
public void mouseDown(MouseEvent e) {
fireMouseDownEvent(e);
}
public void mouseUp(MouseEvent e) {
fireMouseUpEvent(e);
}
};
protected void fireMouseDownEvent(MouseEvent e) {
for (Iterator i = mouseListeners.iterator(); i.hasNext();) {
MouseListener mouseListener = (MouseListener) i.next();
mouseListener.mouseDown(e);
}
}
protected void fireMouseUpEvent(MouseEvent e) {
for (Iterator i = mouseListeners.iterator(); i.hasNext();) {
MouseListener mouseListener = (MouseListener) i.next();
mouseListener.mouseUp(e);
}
}
protected void fireMouseDoubleClickEvent(MouseEvent e) {
for (Iterator i = mouseListeners.iterator(); i.hasNext();) {
MouseListener mouseListener = (MouseListener) i.next();
mouseListener.mouseDoubleClick(e);
}
}
private int computeNewTopRowBasedOnSelection(CalendarableItem newSelection) {
int topRow = compositeTable.getTopRow();
int numberOfRowsInDisplay = compositeTable.getNumRowsVisible();
int newTopRow = topRow;
Point endRowPoint = newSelection.getLowerRightPositionInDayRowCoordinates();
if (endRowPoint != null) {
int endRow = convertDayRowToViewportCoordinates(endRowPoint.y);
if (endRow >= newTopRow + numberOfRowsInDisplay) {
newTopRow += (endRow - (newTopRow + numberOfRowsInDisplay)) + 1;
}
int startRow = newSelection.getUpperLeftPositionInDayRowCoordinates().y;
startRow = convertDayRowToViewportCoordinates(startRow);
if (startRow < newTopRow) {
newTopRow = startRow;
}
}
return newTopRow;
}
private boolean selectCalendarableControlOnSetFocus = true;
private FocusListener cellFocusListener = new FocusAdapter() {
public void focusGained(FocusEvent e) {
TimeSlice sendingRow = (TimeSlice) ((Composite) e.widget).getParent();
int day = sendingRow.getControlColumn(e.widget);
int row = compositeTable.getControlRow(sendingRow);
if (selectCalendarableControlOnSetFocus) {
setSelectionByDayAndRow(day, row, null);
} else {
selectCalendarableControlOnSetFocus = true;
}
}
};
private void setSelectionByDayAndRow(int day, int row, CalendarableItem aboutToSelect) {
int dayRow = convertViewportRowToDayRow(row);
if (aboutToSelect == null && dayRow >= 0)
aboutToSelect = getFirstCalendarableAt(day, dayRow);
if (aboutToSelect == null || dayRow < 0) {
aboutToSelect = getAllDayCalendarableAt(day, row + compositeTable.getTopRow());
}
selectCalenderableControl(aboutToSelect);
aboutToSelect = null;
}
/** (non-API)
* Method getFirstCalendarableAt. Finds the calendarable event at the
* specified day/row in DayRow coordinates. If no calendarable exists
* at the specified coordinates, does nothing.
*
* @param day The day offset
* @param row The row offset in DayRow coordinates
* @return the first Calendarable in the specified (day, row) or null if none.
*/
protected CalendarableItem getFirstCalendarableAt(int day, int row) {
CalendarableItem[][] eventLayout = model.getEventLayout(day);
CalendarableItem selectedCalendarable = null;
for (int column=0; column < eventLayout.length; ++column) {
CalendarableItem calendarable = eventLayout[column][row];
if (calendarable != null) {
if (selectedCalendarable == null) {
selectedCalendarable = calendarable;
} else if (calendarable.getStartTime().after(selectedCalendarable.getStartTime())) {
selectedCalendarable = calendarable;
}
}
}
return selectedCalendarable;
}
/**
* Find the all day event that is positioned at the specified day and row in viewport coordinates
*
* @param day
* @param row
* @return The found Calendarable or null if none
*/
protected CalendarableItem getAllDayCalendarableAt(int day, int row) {
CalendarableItem[] allDayEvents = model.getAllDayCalendarables(day);
for (int allDayEventRow = 0; allDayEventRow < allDayEvents.length; allDayEventRow++) {
CalendarableItem candidate = allDayEvents[allDayEventRow];
if (allDayEventRow == row) {
return candidate;
}
}
// int allDayEventRow = 0;
// for (Iterator calendarablesIter = model.getCalendarableEvents(day).iterator(); calendarablesIter.hasNext();) {
// Calendarable candidate = (Calendarable) calendarablesIter.next();
// if (candidate.isAllDayEvent()) {
// if (allDayEventRow == row) {
// return candidate;
// }
// ++allDayEventRow;
// }
// }
return null;
}
private CalendarableItem selectedCalendarable = null;
/**
* Method selectCalendarable. Selects the specified Calendarable event.
*
* @param newSelection The Calendarable to select.
*/
public void setSelection(CalendarableItem newSelection) {
checkWidget();
if (newSelection != null) {
int day = model.getDay(newSelection);
int row = computeRowForCalendarable(newSelection, day);
selectCalendarableControlOnSetFocus = false;
compositeTable.setSelection(day, row);
selectCalenderableControl(newSelection);
} else {
selectCalenderableControl(null);
}
}
private void selectCalenderableControl(CalendarableItem newSelection) {
if (selectedCalendarable == newSelection) {
return;
}
if (selectedCalendarable != null) {
// The control could be null if it just got scrolled off the screen top or bottom
if (selectedCalendarable.getControl() != null) {
selectedCalendarable.getControl().setSelected(false);
}
}
CalendarableItem oldSelection = selectedCalendarable;
selectedCalendarable = newSelection;
if (newSelection != null && newSelection.getControl() != null) {
newSelection.getControl().setSelected(true);
}
fireSelectionChangeEvent(oldSelection, newSelection);
}
/**
* Method getSelection. Returns the selection. This is computed as follows:
* <ol>
* <li>If a CalendarableItem is currently selected, it is returned.
* <li>If the selection rectangle is in an all-day event row, null is returned.
* <li>Otherwise, the date/time corresponding to the selection rectangle is returned as a java.util.Date.
* </ol>
*
* @return the current DayEditorSelection
*/
public DayEditorSelection getSelection() {
checkWidget();
DayEditorSelection selection = new DayEditorSelection();
Point compositeTableSelection = compositeTable.getSelection();
int visibleAllDayEventRows = model.computeNumberOfAllDayEventRows();
visibleAllDayEventRows -= compositeTable.getTopRow();
if (selectedCalendarable != null) {
selection.setSelectedCalendarable(selectedCalendarable);
if (selectedCalendarable.isAllDayEvent()) {
selection.setAllDay(true);
}
} else {
if (visibleAllDayEventRows > 0) {
if (compositeTableSelection.y < visibleAllDayEventRows) {
selection.setAllDay(true);
}
}
}
selection.setDateTime(computeDateTimeFromViewportCoordinates(
compositeTableSelection, visibleAllDayEventRows));
return selection;
}
private List selectionChangeListeners = new ArrayList();
private void fireSelectionChangeEvent(CalendarableItem currentSelection, CalendarableItem newSelection) {
SelectionChangeEvent sce = new SelectionChangeEvent(currentSelection, newSelection);
for (Iterator listenersIter = selectionChangeListeners.iterator(); listenersIter.hasNext();) {
CalendarableSelectionChangeListener listener = (CalendarableSelectionChangeListener) listenersIter.next();
listener.selectionChanged(sce);
}
}
/**
* Adds the listener to the collection of listeners who will
* be notified when the receiver's selection changes, by sending
* it one of the messages defined in the <code>CalendarableSelectionChangeListener</code>
* interface.
* <p>
* <code>selectionChanged</code> is called when the selection changes.
* </p>
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableSelectionChangeListener
* @see #removeSelectionChangeListener
* @see SelectionChangeEvent
*/
public void addSelectionChangeListener(CalendarableSelectionChangeListener l) {
checkWidget();
if (l == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
selectionChangeListeners.add(l);
}
private boolean fireEvents(CalendarableItem calendarableItem, List handlers) {
CalendarableItemEvent e = new CalendarableItemEvent();
e.calendarableItem = calendarableItem;
for (Iterator iter = handlers.iterator(); iter.hasNext();) {
CalendarableItemEventHandler handler = (CalendarableItemEventHandler) iter.next();
handler.handleRequest(e);
if (!e.doit) {
break;
}
}
for (Iterator i = handlers.iterator(); i.hasNext();) {
CalendarableItemEventHandler h = (CalendarableItemEventHandler) i.next();
h.requestHandled(e);
if (!e.doit) {
break;
}
}
return e.doit;
}
private List editHandlers = new ArrayList();
/**
* Fire the itemEdit event.
*
* @param toEdit The CalendarableItem to edit.
* @return true if the object represented by the CalendarableItem was changed; false otherwise.
*/
public boolean fireEdit(CalendarableItem toEdit) {
checkWidget();
CalendarableItemEvent e = new CalendarableItemEvent();
e.calendarableItem = toEdit;
boolean changed = fireEvents(e, editHandlers);
if (changed) {
// NEBULA_TODO: only refresh the days that are necessary
refresh();
}
return changed;
}
/**
* Adds the handler to the collection of handlers who will hand editing of
* calendarable events, by sending it one of the messages defined in the
* <code>CalendarableItemInsertHandler</code> abstract class.
* <p>
* <code>itemInserted</code> is called when the CalendarableItem is
* inserted.
* </p>
*
* @param handler
* the handler which should be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* </ul>
*
* @see CalendarableItemInsertHandler
* @see #removeItemInsertHandler
*/
public void addItemEditHandler(CalendarableItemEventHandler handler) {
checkWidget();
if (handler == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
editHandlers.add(handler);
}
/**
* Removes the handler from the collection of handlers who will hand editing of
* calendarable events, by sending it one of the messages defined in the
* <code>CalendarableItemInsertHandler</code> abstract class.
* <p>
* <code>itemInserted</code> is called when the CalendarableItem is
* inserted.
* </p>
*
* @param handler
* the handler which should be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* </ul>
*
* @see CalendarableItemInsertHandler
* @see #removeItemInsertHandler
*/
public void removeItemEditHandler(CalendarableItemEventHandler handler) {
checkWidget();
if (handler == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
editHandlers.remove(handler);
}
private List deleteHandlers = new ArrayList();
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#fireDelete(org.eclipse.jface.examples.databinding.compositetable.timeeditor.CalendarableItem)
*/
public boolean fireDelete(CalendarableItem item) {
checkWidget();
boolean result = fireEvents(item, deleteHandlers);
if (result) {
// NEBULA_TODO: Only refresh the affected days.
refresh();
}
return result;
}
/**
* Adds the handler to the collection of handlers who will
* be notified when a CalendarableItem is deleted from the receiver, by sending
* it one of the messages defined in the <code>CalendarableItemEventHandler</code>
* abstract class.
* <p>
* <code>itemDeleted</code> is called when the CalendarableItem is deleted.
* </p>
*
* @param handler the handler which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableItemEventHandler
* @see #removeDeleteItemHandler
*/
public void addItemDeleteHandler(CalendarableItemEventHandler handler) {
checkWidget();
if (handler == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
deleteHandlers.add(handler);
}
/**
* Removes the handler from the collection of handlers who will
* be notified when a CalendarableItem is deleted from the receiver, by sending
* it one of the messages defined in the <code>CalendarableItemEventHandler</code>
* abstract class.
* <p>
* <code>itemDeleted</code> is called when the CalendarableItem is deleted.
* </p>
*
* @param handler the handler which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableItemEventHandler
* @see #addDeleteItemHandler
*/
public void removeItemDeleteHandler(CalendarableItemEventHandler handler) {
checkWidget();
deleteHandlers.remove(handler);
}
private List itemDisposeHandlers = new ArrayList();
private boolean fireDisposeItemStrategy(CalendarableItem item) {
return fireEvents(item, itemDisposeHandlers);
}
/**
* Adds the handler to the collection of handler who will
* be notified when a CalendarableItem's control is disposed, by sending
* it one of the messages defined in the <code>CalendarableItemEventHandler</code>
* abstract class. This is normally used to remove any data bindings
* that may be attached to the (now-unused) CalendarableItem.
* <p>
* <code>itemDeleted</code> is called when the CalendarableItem is deleted.
* </p>
*
* @param handler the handler which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableItemEventHandler
* @see #removeCalendarableItemDisposeHandler
*/
public void addItemDisposeHandler(CalendarableItemEventHandler handler) {
checkWidget();
if (handler == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
itemDisposeHandlers.add(handler);
}
/**
* Removes the handler from the collection of handlers who will
* be notified when a CalendarableItem is disposed, by sending
* it one of the messages defined in the <code>CalendarableItemEventHandler</code>
* abstract class. This is normally used to remove any data bindings
* that may be attached to the (now-unused) CalendarableItem.
* <p>
* <code>itemDeleted</code> is called when the CalendarableItem is deleted.
* </p>
*
* @param handler the handler which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the handler is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableItemEventHandler
* @see #removeDeleteListener
*/
public void removeItemDisposeHandler(CalendarableItemEventHandler handler) {
checkWidget();
itemDisposeHandlers.remove(handler);
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the receiver's selection changes, by sending
* it one of the messages defined in the <code>CalendarableSelectionChangeListener</code>
* interface.
* <p>
* <code>selectionChanged</code> is called when the selection changes.
* </p>
*
* @param listener the listener which should no longer be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see CalendarableSelectionChangeListener
* @see #addSelectionChangeListener
* @see SelectionChangeEvent
*/
public void removeSelectionChangeListener(CalendarableSelectionChangeListener l) {
checkWidget();
if (l == null) {
throw new IllegalArgumentException("The argument cannot be null");
}
if (isDisposed()) {
throw new SWTException("Widget is disposed");
}
selectionChangeListeners.remove(l);
}
/**
* @return Returns the defaultStartHour.
*/
public int getDefaultStartHour() {
return model.getDefaultStartHour();
}
/**
* @param defaultStartHour The defaultStartHour to set.
*/
public void setDefaultStartHour(int defaultStartHour) {
checkWidget();
model.setDefaultStartHour(defaultStartHour);
updateVisibleRows();
layoutEventControls();
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#setDayEventCountProvider(org.eclipse.jface.examples.databinding.compositetable.timeeditor.EventCountProvider)
*/
public void setEventCountProvider(EventCountProvider eventCountProvider) {
checkWidget();
model.setEventCountProvider(eventCountProvider);
updateVisibleRows();
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
if (isDisposed()) return;
layoutEventControls();
}
});
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#setEventContentProvider(org.eclipse.jface.examples.databinding.compositetable.timeeditor.EventContentProvider)
*/
public void setEventContentProvider(EventContentProvider eventContentProvider) {
checkWidget();
model.setEventContentProvider(eventContentProvider);
updateVisibleRows();
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
if (isDisposed()) return;
layoutEventControls();
}
});
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#setStartDate(java.util.Date)
*/
public void setStartDate(Date startDate) {
checkWidget();
List removedDays = model.setStartDate(startDate);
computeEventRowsForNewDays();
if (daysHeader != null) {
refreshColumnHeaders(daysHeader.getColumns());
}
updateVisibleRows();
freeObsoleteCalendarableEventControls(removedDays);
if (compositeTable.getNumRowsVisible() > 0) {
layoutEventControls();
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#getStartDate()
*/
public Date getStartDate() {
checkWidget();
return model.getStartDate();
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#refresh(java.util.Date)
*/
public void refresh(Date date) {
checkWidget();
computeLayoutFor(date);
layoutEventControls();
}
private void computeLayoutFor(Date date) {
List removedDays = model.refresh(date);
freeObsoleteCalendarableEventControls(removedDays);
updateVisibleRows();
computeEventRowsForDate(date);
}
private boolean refreshing = false;
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#refresh()
*/
public void refresh() {
checkWidget();
if (!refreshing) {
refreshing = true;
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
if (isDisposed()) return;
Date dateToRefresh = getStartDate();
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(dateToRefresh);
for (int i=0; i < getNumberOfDays(); ++i) {
computeLayoutFor(gc.getTime());
gc.add(Calendar.DATE, 1);
}
layoutEventControls();
refreshing = false;
}
});
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#getNumberOfDays()
*/
public int getNumberOfDays() {
checkWidget();
return model.getNumberOfDays();
}
/* (non-Javadoc)
* @see org.eclipse.jface.examples.databinding.compositetable.timeeditor.IEventEditor#getNumberOfDivisionsInHour()
*/
public int getNumberOfDivisionsInHour() {
checkWidget();
return model.getNumberOfDivisionsInHour();
}
// Display Refresh logic here ----------------------------------------------
/*
* There are four main coordinate systems the refresh algorithm has to
* deal with:
*
* 1) Rows starting from midnight (the way the DayModel computes the layout).
* These are called Day Row coordinates.
*
* 2) Rows starting from the top visible row, taking into account all-day
* event rows. These are called Viewport Row coordinates
*
* 3) Pixel coordinates for each TimeSlot, relative to its parent TimeSlice
* (the CompositeTable row object) row. This is relevant because these
* are transformed into #4 in order to place CalendarableEventControls.
*
* 4) Pixel coordinates relative to the top left (the origin) of the entire
* DayEditor control.
*/
private int numberOfAllDayEventRows = 0;
Calendar calendar = new GregorianCalendar();
private int computeNumRowsInCollection(final int numberOfDivisionsInHour) {
numberOfAllDayEventRows = model.computeNumberOfAllDayEventRows();
return (DISPLAYED_HOURS-model.computeStartHour()) * numberOfDivisionsInHour+numberOfAllDayEventRows;
}
private int convertViewportRowToDayRow(int row) {
int topRowOffset = compositeTable.getTopRow() - numberOfAllDayEventRows;
int startOfDayOffset = model.computeStartHour() * model.getNumberOfDivisionsInHour();
return row + topRowOffset + startOfDayOffset;
}
private int convertDayRowToViewportCoordinates(int row) {
row -= model.computeStartHour() * model.getNumberOfDivisionsInHour()
- numberOfAllDayEventRows;
return row;
}
private Date computeDateTimeFromViewportCoordinates(Point viewportSelection, int visibleAllDayEventRows) {
Date startDate = model.calculateDate(getStartDate(), viewportSelection.x);
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(startDate);
calendar.set(Calendar.HOUR_OF_DAY,
model.computeHourFromRow(viewportSelection.y - visibleAllDayEventRows));
calendar.set(Calendar.MINUTE,
model.computeMinuteFromRow(viewportSelection.y - visibleAllDayEventRows));
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
/**
* @param calendarable
* @param day
* @return The row in DayRow coordinates
*/
private int computeRowForCalendarable(CalendarableItem calendarable, int day) {
int row = 0;
if (calendarable.isAllDayEvent()) {
CalendarableItem[] allDayEvents = model.getAllDayCalendarables(day);
for (int allDayEventRow = 0; allDayEventRow < allDayEvents.length; allDayEventRow++) {
if (allDayEvents[allDayEventRow] == calendarable) {
row = allDayEventRow - compositeTable.getTopRow();
break;
}
}
} else {
// Convert to viewport coordinates
Point upperLeft = calendarable.getUpperLeftPositionInDayRowCoordinates();
int topRowOffset = compositeTable.getTopRow() - numberOfAllDayEventRows;
int startOfDayOffset = model.computeStartHour() * model.getNumberOfDivisionsInHour();
row = upperLeft.y - topRowOffset - startOfDayOffset;
if (row < 0) {
row = 0;
}
}
return row;
}
/*
* Update the number of rows that are displayed inside the CompositeTable control
*/
private void updateVisibleRows() {
compositeTable.setNumRowsInCollection(computeNumRowsInCollection(getNumberOfDivisionsInHour()));
}
private void refreshRow(int currentObjectOffset, TimeSlice timeSlice) {
// Decrement currentObjectOffset for each all-day event line we need.
for (int allDayEventRow = 0; allDayEventRow < numberOfAllDayEventRows; ++allDayEventRow) {
--currentObjectOffset;
}
if (currentObjectOffset < 0) {
timeSlice.setCurrentTime(null);
} else {
calendar.set(Calendar.HOUR_OF_DAY,
model.computeHourFromRow(currentObjectOffset));
calendar.set(Calendar.MINUTE,
model.computeMinuteFromRow(currentObjectOffset));
timeSlice.setCurrentTime(calendar.getTime());
}
}
/**
* (non-API) Method initializeColumnHeaders. Called internally when the
* column header text needs to be updated.
*
* @param columns A LinkedList of CLabels representing the column objects
*/
protected void refreshColumnHeaders(LinkedList columns) {
Date startDate = getStartDate();
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(startDate);
SimpleDateFormat formatter = new SimpleDateFormat("EE, MMM d");
formatter.applyLocalizedPattern(formatter.toLocalizedPattern());
for (Iterator iter = columns.iterator(); iter.hasNext();) {
CLabel headerLabel = (CLabel) iter.next();
headerLabel.setText(formatter.format(gc.getTime()));
gc.add(Calendar.DATE, 1);
}
}
private void freeObsoleteCalendarableEventControls(List removedCalendarables) {
for (Iterator removedCalendarablesIter = removedCalendarables.iterator(); removedCalendarablesIter.hasNext();) {
CalendarableItem toRemove = (CalendarableItem) removedCalendarablesIter.next();
if (selectedCalendarable == toRemove) {
setSelection(null);
}
freeCalendarableControl(toRemove);
}
}
private void computeEventRowsForDate(Date date) {
GregorianCalendar targetDate = new GregorianCalendar();
targetDate.setTime(date);
GregorianCalendar target = new GregorianCalendar();
target.setTime(model.getStartDate());
EventLayoutComputer dayModel = new EventLayoutComputer(model.getNumberOfDivisionsInHour());
for (int dayOffset=0; dayOffset < model.getNumberOfDays(); ++dayOffset) {
if (target.get(Calendar.DATE) == targetDate.get(Calendar.DATE) &&
target.get(Calendar.MONTH) == targetDate.get(Calendar.MONTH) &&
target.get(Calendar.YEAR) == targetDate.get(Calendar.YEAR))
{
computeEventLayout(dayModel, dayOffset);
break;
}
target.add(Calendar.DATE, 1);
}
}
private void computeEventRowsForNewDays() {
EventLayoutComputer dayModel = new EventLayoutComputer(model.getNumberOfDivisionsInHour());
for (int dayOffset=0; dayOffset < model.getNumberOfDays(); ++dayOffset) {
if (model.getNumberOfColumnsWithinDay(dayOffset) == -1) {
computeEventLayout(dayModel, dayOffset);
}
}
}
private void computeEventLayout(EventLayoutComputer dayModel, int dayOffset) {
List events = model.getCalendarableItems(dayOffset);
CalendarableItem[][] eventLayout = dayModel.computeEventLayout(events);
model.setEventLayout(dayOffset, eventLayout);
}
private void layoutEventControlsDeferred() {
if (getStartDate() == null) {
return;
}
refreshEventControlPositions.run();
Display.getCurrent().asyncExec(refreshEventControlPositions);
}
private void layoutEventControls() {
if (getStartDate() == null) {
return;
}
refreshEventControlPositions.run();
}
private Runnable refreshEventControlPositions = new Runnable() {
public void run() {
if (isDisposed()) return;
Control[] gridRows = compositeTable.getRowControls();
for (int day=0; day < model.getNumberOfDays(); ++day) {
int columnsWithinDay = model.getNumberOfColumnsWithinDay(day);
Point[] columnPositions = computeColumns(day, columnsWithinDay, gridRows);
int allDayEventRow = 0;
for (Iterator calendarablesIter = model.getCalendarableItems(day).iterator(); calendarablesIter.hasNext();) {
CalendarableItem calendarable = (CalendarableItem) calendarablesIter.next();
if (calendarable.isAllDayEvent()) {
layoutAllDayEvent(day, allDayEventRow, calendarable, gridRows);
++allDayEventRow;
} else {
layoutTimedEvent(day, columnPositions, calendarable, gridRows);
}
}
}
}
};
protected Point[] computeColumns(int day, int numberOfColumns, Control[] gridRows) {
Point[] columns = new Point[numberOfColumns];
Rectangle timeSliceBounds = getTimeSliceBounds(day, compositeTable.getTopRow(), gridRows);
timeSliceBounds.x += TimeSlot.TIME_BAR_WIDTH + 1;
timeSliceBounds.width -= TimeSlot.TIME_BAR_WIDTH + 2;
int baseWidth = timeSliceBounds.width / numberOfColumns;
int extraWidth = timeSliceBounds.width % numberOfColumns;
int startingPosition = timeSliceBounds.x;
for (int column = 0; column < columns.length; column++) {
int columnStart = startingPosition;
int columnWidth = baseWidth;
if (extraWidth > 0) {
++columnWidth;
--extraWidth;
}
columns[column] = new Point(columnStart, columnWidth);
startingPosition += columnWidth;
}
return columns;
}
private void fillControlData(CalendarableItem calendarable, int clippingStyle) {
calendarable.getControl().setText(calendarable.getText());
calendarable.getControl().setToolTipText(calendarable.getToolTipText());
calendarable.getControl().setClipping(clippingStyle);
}
private DayEditorCalendarableItemControl getControl(CalendarableItem item) {
return (DayEditorCalendarableItemControl) item.getControl();
}
private void layoutAllDayEvent(int day, int allDayEventRow, CalendarableItem calendarable, Control[] gridRows) {
if (eventRowIsVisible(allDayEventRow)) {
createCalendarableControl(calendarable);
fillControlData(calendarable, SWT.NULL);
Rectangle timeSliceBounds = getTimeSliceBounds(day, allDayEventRow, gridRows);
int gutterWidth = TimeSlot.TIME_BAR_WIDTH + 1;
timeSliceBounds.x += gutterWidth;
timeSliceBounds.width -= gutterWidth;
getControl(calendarable).setBounds(timeSliceBounds);
getControl(calendarable).moveAbove(compositeTable);
} else {
freeCalendarableControl(calendarable);
}
}
private void layoutTimedEvent(int day, Point[] columnPositions, CalendarableItem calendarable, Control[] gridRows) {
int firstVisibleRow = model.computeStartHour() * model.getNumberOfDivisionsInHour();
int scrolledRows = compositeTable.getTopRow() - numberOfAllDayEventRows;
int visibleAllDayEventRows = 0;
if (scrolledRows < 0) {
visibleAllDayEventRows = -1 * scrolledRows;
scrolledRows = 0;
}
firstVisibleRow += scrolledRows;
int lastVisibleRow = firstVisibleRow + compositeTable.getNumRowsVisible() - visibleAllDayEventRows - 1;
int startRow = calendarable.getUpperLeftPositionInDayRowCoordinates().y;
int endRow = calendarable.getLowerRightPositionInDayRowCoordinates().y;
if (timedEventIsVisible(firstVisibleRow, lastVisibleRow, startRow, endRow)) {
int clippingStyle = SWT.NULL;
if (startRow < firstVisibleRow) {
startRow = firstVisibleRow;
clippingStyle |= SWT.TOP;
}
if (endRow > lastVisibleRow) {
endRow = lastVisibleRow;
clippingStyle |= SWT.BOTTOM;
}
startRow = convertDayRowToViewportCoordinates(startRow);
endRow = convertDayRowToViewportCoordinates(endRow);
createCalendarableControl(calendarable);
fillControlData(calendarable, clippingStyle);
Rectangle startRowBounds = getTimeSliceBounds(day, startRow, gridRows);
Rectangle endRowBounds = getTimeSliceBounds(day, endRow, gridRows);
int leftmostColumn = calendarable.getUpperLeftPositionInDayRowCoordinates().x;
int rightmostColumn = calendarable.getLowerRightPositionInDayRowCoordinates().x;
int left = columnPositions[leftmostColumn].x;
int top = startRowBounds.y + 1;
int width = columnPositions[rightmostColumn].x - columnPositions[leftmostColumn].x + columnPositions[rightmostColumn].y;
int height = endRowBounds.y - startRowBounds.y + endRowBounds.height - 1;
Rectangle finalPosition = new Rectangle(left, top, width, height);
getControl(calendarable).setBounds(finalPosition);
getControl(calendarable).moveAbove(compositeTable);
} else {
freeCalendarableControl(calendarable);
}
}
private boolean eventRowIsVisible(int eventRow) {
int topRow = compositeTable.getTopRow();
if (topRow <= eventRow) {
if (eventRow < compositeTable.getNumRowsVisible() - topRow) {
return true;
}
}
return false;
}
private boolean timedEventIsVisible(int firstVisibleRow, int lastVisibleRow, int startRow, int endRow) {
if (startRow < firstVisibleRow && endRow < firstVisibleRow)
return false;
if (startRow > lastVisibleRow && endRow > lastVisibleRow)
return false;
return true;
}
private void createCalendarableControl(CalendarableItem calendarable) {
if (calendarable.getControl() == null) {
calendarable.setControl(newCEC());
if (calendarable == selectedCalendarable) {
calendarable.getControl().setSelected(true);
}
}
}
private Rectangle getTimeSliceBounds(int day, int eventRow, Control[] gridRows) {
int row = eventRow - compositeTable.getTopRow();
TimeSlice rowObject = (TimeSlice) gridRows[row];
Control slot = rowObject.getColumnControl(day);
return getBoundsInDayEditorCoordinates(slot);
}
private void freeCalendarableControl(CalendarableItem calendarableItem) {
if (calendarableItem.getControl() != null) {
freeCEC(getControl(calendarableItem));
calendarableItem.setControl(null);
fireDisposeItemStrategy(calendarableItem);
}
}
private Rectangle getBoundsInDayEditorCoordinates(Control slot) {
return Display.getCurrent().map(slot.getParent(), this, slot.getBounds());
}
// CalendarableItemControl construction/destruction here -----------------
MouseAdapter selectCompositeTableOnMouseDownAdapter = new MouseAdapter() {
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseAdapter#mouseDown(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDown(MouseEvent e) {
fireMouseDownEvent(e);
ICalendarableItemControl control = (ICalendarableItemControl) e.widget;
CalendarableItem aboutToSelect = control.getCalendarableItem();
setSelection(aboutToSelect);
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseAdapter#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDoubleClick(MouseEvent e) {
fireMouseDoubleClickEvent(e);
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseAdapter#mouseUp(org.eclipse.swt.events.MouseEvent)
*/
public void mouseUp(MouseEvent e) {
fireMouseUpEvent(e);
}
};
private DayEditorCalendarableItemControl newCEC() {
if (recycledCalendarableEventControls.size() > 0) {
DayEditorCalendarableItemControl result = (DayEditorCalendarableItemControl) recycledCalendarableEventControls.remove(0);
result.setVisible(true);
return result;
}
DayEditorCalendarableItemControl dayEditorCalendarableItemControl = new DayEditorCalendarableItemControl(this, SWT.NULL);
if (menu != null) {
dayEditorCalendarableItemControl.setMenu(menu);
}
dayEditorCalendarableItemControl.addMouseListener(selectCompositeTableOnMouseDownAdapter);
return dayEditorCalendarableItemControl;
}
private void freeCEC(DayEditorCalendarableItemControl control) {
control.setSelected(false);
control.setCalendarableItem(null);
control.setVisible(false);
recycledCalendarableEventControls.add(control);
}
private Color background = null;
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Control#setBackground(org.eclipse.swt.graphics.Color)
*/
public void setBackground(Color color) {
checkWidget();
super.setBackground(color);
this.background = color;
if (compositeTable != null) {
compositeTable.setBackground(color);
}
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Composite#setFocus()
*/
public boolean setFocus() {
checkWidget();
if (!compositeTable.setFocus()) {
return super.setFocus();
}
return true;
}
} // @jve:decl-index=0:visual-constraint="10,10"