diff --git a/.classpath b/.classpath
index d563d4a..f931a93 100644
--- a/.classpath
+++ b/.classpath
@@ -9,8 +9,6 @@
-
-
@@ -32,18 +30,8 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.settings/com.google.appengine.eclipse.core.prefs b/.settings/com.google.appengine.eclipse.core.prefs
new file mode 100644
index 0000000..82c36af
--- /dev/null
+++ b/.settings/com.google.appengine.eclipse.core.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+filesCopiedToWebInfLib=
diff --git a/.settings/com.google.gwt.eclipse.core.prefs b/.settings/com.google.gwt.eclipse.core.prefs
index 5b1cbfa..5b45b46 100644
--- a/.settings/com.google.gwt.eclipse.core.prefs
+++ b/.settings/com.google.gwt.eclipse.core.prefs
@@ -1,3 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
+filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBW11dPjwvZXh0cmEtYXJncz48dm0tYXJncz48IVtDREFUQVstWG14NTEybV1dPjwvdm0tYXJncz48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5iaW9tZWQuQmlvbWVkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
diff --git a/server.properties b/server.properties
new file mode 100644
index 0000000..1ba55a2
--- /dev/null
+++ b/server.properties
@@ -0,0 +1,7 @@
+email.username=schedule
+email.password=success4
+database.host=biomed.akira.gs:3306
+database.name=biomed_devel
+database.username=biomed_devel
+database.password=pXVt9QMSq8XURfXm
+devmode=true
\ No newline at end of file
diff --git a/src/com/allen_sauer/gwt/dnd/client/drop/DayViewDropController.java b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewDropController.java
new file mode 100644
index 0000000..0a78d48
--- /dev/null
+++ b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewDropController.java
@@ -0,0 +1,181 @@
+package com.allen_sauer.gwt.dnd.client.drop;
+
+import java.util.Date;
+
+import com.allen_sauer.gwt.dnd.client.DragContext;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.dayview.AppointmentWidget;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class DayViewDropController extends AbsolutePositionDropController {
+
+ private int gridX;
+
+ private int gridY;
+ int dayStartsAt = 0;
+ private int intervalsPerHour;
+ private int snapSize;
+ private int columns;
+ private int rows;
+ private Date date;
+ private int maxProxyHeight = -1;
+
+ public void setColumns(final int columns) {
+ this.columns = columns;
+ }
+
+ public void setDate(final Date date) {
+ this.date = date;
+ }
+
+ public void setSnapSize(final int snapSize) {
+ this.snapSize = snapSize;
+ }
+
+ public void setIntervalsPerHour(final int intervalsPerHour) {
+ this.intervalsPerHour = intervalsPerHour;
+ this.rows = intervalsPerHour * 24;
+ }
+
+ public void setDayStartsAt(final int dayStartsAt) {
+ this.dayStartsAt = dayStartsAt;
+ }
+
+ public void setMaxProxyHeight(final int maxProxyHeight) {
+ this.maxProxyHeight = maxProxyHeight;
+ }
+
+ public DayViewDropController(final AbsolutePanel dropTarget) {
+ super(dropTarget);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onDrop(final DragContext context) {
+
+ super.onDrop(context);
+
+ //get the top and left position and the widget
+ int top =draggableList.get(0).desiredY;
+ int left=draggableList.get(0).desiredX;
+ Widget widget = context.draggable;
+
+ //set the 'snapped' top and left position of the widget
+ left = Math.max(0, Math.min(left, dropTarget.getOffsetWidth() - widget.getOffsetWidth()));
+ top = Math.max(0, Math.min(top, dropTarget.getOffsetHeight() - widget.getOffsetHeight()));
+ left = Math.round((float) left / gridX) * gridX;
+ top = Math.round((float) top / gridY) * gridY;
+
+ //figure out which row the appointment was dragged to
+ int intervalStart = (int) Math.floor(top / gridY);
+ int intervalSpan = Math.round(widget.getOffsetHeight() / snapSize);
+
+ //figure out which day (column) the appointment was dragged to
+ int day = (int) Math.floor(left / gridX);
+ day = Math.max(0, day);
+ day = Math.min(day, columns - 1);
+
+ //get the appointment, create the start & end date
+ Appointment appt = ((AppointmentWidget)widget).getAppointment();
+ Date start = (Date)date.clone();
+ Date end = (Date)date.clone();
+ start.setDate(start.getDate()+day);
+ end.setDate(end.getDate()+day);
+
+ start.setHours(dayStartsAt);
+ start.setMinutes(0);
+ start.setSeconds(0);
+ start.setMinutes((intervalStart)*(60/intervalsPerHour));
+ end.setHours(dayStartsAt);
+ end.setMinutes(0);
+ end.setSeconds(0);
+ end.setMinutes((intervalStart + intervalSpan)*(60/intervalsPerHour));
+
+
+ appt.setStart(start);
+ appt.setEnd(end);
+
+
+
+ }
+
+
+// @Override
+// public void drop(Widget widget, int left, int top) {
+//
+// }
+
+// @Override
+// public void drop(Widget widget, int left, int top) {
+// left = Math.max(0, Math.min(left, dropTarget.getOffsetWidth() - widget.getOffsetWidth()));
+// top = Math.max(0, Math.min(top, dropTarget.getOffsetHeight() - widget.getOffsetHeight()));
+// left = Math.round((float) left / gridX) * gridX;
+// top = Math.round((float) top / gridY) * gridY;
+//
+// System.out.println("on drop");
+//
+//
+// int intervalStart = (int) Math.floor(top / rows);
+// int intervalSpan = 2;
+// int day = (int) Math.floor(left / columns);
+// day = Math.min(0, day);
+// day = Math.min(day, columns);
+// day = day-1; //convert to a 0-based day index
+//
+// Appointment appt = ((AppointmentWidget)widget).getAppointment();
+// Date start = (Date)date.clone();
+// Date end = (Date)date.clone();
+//
+// start.setDate(start.getDate()+day);
+// end.setDate(end.getDate()+day);
+//
+// start.setHours(0);
+// start.setMinutes((intervalStart)*(60/intervalsPerHour));
+// end.setHours(0);
+// end.setMinutes((intervalStart + intervalSpan)*(60/intervalsPerHour));
+//
+// System.out.println("new start: "+start);
+//
+// appt.setStart(start);
+// appt.setEnd(end);
+//
+//
+// }
+
+ @Override
+ public void onMove(final DragContext context) {
+ super.onMove(context);
+
+ gridX = (int) Math.floor(dropTarget.getOffsetWidth() / columns);
+ gridY = (int) Math.floor(dropTarget.getOffsetHeight() / rows);
+
+
+ for (Draggable draggable : draggableList) {
+ draggable.desiredX = context.desiredDraggableX - dropTargetOffsetX + draggable.relativeX;
+ draggable.desiredY = context.desiredDraggableY - dropTargetOffsetY + draggable.relativeY;
+
+ draggable.desiredX = Math.max(0, Math.min(draggable.desiredX, dropTargetClientWidth - draggable.offsetWidth));
+ draggable.desiredY = Math.max(0, Math.min(draggable.desiredY, dropTargetClientHeight - draggable.offsetHeight));
+ draggable.desiredX = (int)Math.floor((double) draggable.desiredX / gridX) * gridX;
+ draggable.desiredY = (int)Math.round((double) draggable.desiredY / gridY) * gridY;
+
+ dropTarget.add(draggable.positioner, draggable.desiredX, draggable.desiredY);
+ }
+ }
+
+ @Override
+ public void onEnter(final DragContext context) {
+ super.onEnter(context);
+
+ for (Draggable draggable : draggableList) {
+ int width = draggable.positioner.getOffsetWidth();
+ int height = draggable.positioner.getOffsetHeight();
+ if (maxProxyHeight > 0 && height > maxProxyHeight) {
+ height = maxProxyHeight - 5;
+ }
+
+ draggable.positioner.setPixelSize(width, height);
+ }
+ }
+}
diff --git a/src/com/allen_sauer/gwt/dnd/client/drop/DayViewPickupDragController.java b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewPickupDragController.java
new file mode 100644
index 0000000..0c85e71
--- /dev/null
+++ b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewPickupDragController.java
@@ -0,0 +1,55 @@
+package com.allen_sauer.gwt.dnd.client.drop;
+
+import com.allen_sauer.gwt.dnd.client.DragContext;
+import com.allen_sauer.gwt.dnd.client.PickupDragController;
+import com.allen_sauer.gwt.dnd.client.util.DragClientBundle;
+import com.allen_sauer.gwt.dnd.client.util.WidgetArea;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class DayViewPickupDragController extends PickupDragController {
+
+ private int maxProxyHeight = -1;
+
+ public DayViewPickupDragController(AbsolutePanel boundaryPanel,
+ boolean allowDroppingOnBoundaryPanel) {
+ super(boundaryPanel, allowDroppingOnBoundaryPanel);
+ }
+
+ @Override
+ public void dragMove() {
+ try {
+ super.dragMove();
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ @Override
+ protected Widget newDragProxy(DragContext context) {
+ AbsolutePanel container = new AbsolutePanel();
+ container.getElement().getStyle().setProperty("overflow", "visible");
+
+ WidgetArea draggableArea = new WidgetArea(context.draggable, null);
+ for (Widget widget : context.selectedWidgets) {
+ WidgetArea widgetArea = new WidgetArea(widget, null);
+ Widget proxy = new SimplePanel();
+ int height = widget.getOffsetHeight();
+ if (maxProxyHeight > 0 && height > maxProxyHeight) {
+ height = maxProxyHeight - 5;
+ }
+
+ proxy.setPixelSize(widget.getOffsetWidth(), height);
+ proxy.addStyleName(DragClientBundle.INSTANCE.css().proxy());
+ container.add(proxy,
+ widgetArea.getLeft() - draggableArea.getLeft(),
+ widgetArea.getTop() - draggableArea.getTop());
+ }
+
+ return container;
+ }
+
+ public void setMaxProxyHeight(int maxProxyHeight) {
+ this.maxProxyHeight = maxProxyHeight;
+ }
+}
diff --git a/src/com/allen_sauer/gwt/dnd/client/drop/DayViewResizeController.java b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewResizeController.java
new file mode 100644
index 0000000..7a42c08
--- /dev/null
+++ b/src/com/allen_sauer/gwt/dnd/client/drop/DayViewResizeController.java
@@ -0,0 +1,85 @@
+package com.allen_sauer.gwt.dnd.client.drop;
+
+import java.util.Date;
+
+import com.allen_sauer.gwt.dnd.client.AbstractDragController;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.dayview.AppointmentWidget;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class DayViewResizeController extends AbstractDragController {
+
+ public DayViewResizeController(AbsolutePanel boundaryPanel) {
+ super(boundaryPanel);
+ }
+
+ int dayStartsAt = 0;
+ int snapSize;
+ int intervalsPerHour;
+
+ public void setSnapSize(int snapSize) {
+ this.snapSize = snapSize;
+ }
+
+ public void setIntervalsPerHour(int intervalsPerHour) {
+ this.intervalsPerHour = intervalsPerHour;
+ }
+
+ public void setDayStartsAt(int dayStartsAt) {
+ this.dayStartsAt = dayStartsAt;
+ }
+
+ public void dragMove() {
+
+ Widget appointment = context.draggable.getParent();
+
+ //calculates difference between elements position on screen
+ // and how many pixels the user is trying to drag it
+ int delta = context.draggable.getAbsoluteTop()
+ - context.desiredDraggableY;
+
+ //get the height of the widget
+ int contentHeight = appointment.getOffsetHeight();
+
+ //make sure the height of the widget is not < the minimum size
+ int newHeight = Math.max(contentHeight - delta, snapSize);
+
+ //get the 'snapped' height. basically it gets the rounded
+ // intervals spanned, then multiples it by the snapSize
+ int snapHeight = Math.round(
+ (float) newHeight / snapSize) * snapSize;
+
+ appointment.setHeight(snapHeight + "px");
+ }
+
+
+ @SuppressWarnings("deprecation")
+ public void dragEnd() {
+ AppointmentWidget apptWidget = (AppointmentWidget)context.draggable.getParent();
+ int apptHeight = apptWidget.getOffsetHeight();
+ Appointment appt = apptWidget.getAppointment();
+
+
+ //get the start date
+ Date end = (Date)appt.getStart().clone();
+
+ //get the "top" location of the appointment widget
+ float topFloat = DOM.getIntStyleAttribute(apptWidget.getElement(), "top");
+
+ //get the grid span
+ //int intervalStart = Math.round(topFloat / snapSize);
+ int intervalSpan = Math.round(apptHeight / snapSize);
+
+ //set the end based on the new dragged value
+ //end.setHours(dayStartsAt);
+ end.setMinutes(end.getMinutes() + intervalSpan * (60 / intervalsPerHour));
+
+ //update the end
+ appt.setEnd(end);
+
+ super.dragEnd();
+ }
+
+}
diff --git a/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewDropController.java b/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewDropController.java
new file mode 100644
index 0000000..d84ab4d
--- /dev/null
+++ b/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewDropController.java
@@ -0,0 +1,227 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009,2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see Appointment is being dragged.
+ *
+ * @param context An object providing information about the object being
+ * dragged
+ */
+ @Override
+ public void onMove(final DragContext context) {
+ //super.onMove(context);
+
+ //get the draggable object
+ Draggable draggable = draggableList.get(0);
+
+ //make sure it isn't null (shouldn't ever be)
+ if (draggable == null)
+ return;
+
+ int col = getColumn(context, draggable);
+ int row = getRow(context, draggable);
+
+ Element currHoveredCell =
+ monthGrid.getFlexCellFormatter().getElement(row, col);
+
+ //If this cell isn't already highlighted, we need to highlight
+ if (highlightedCells == null || highlightedCells.length < 0 ||
+ !currHoveredCell.equals(highlightedCells[0])) {
+
+ if (highlightedCells != null) {
+ for (Element elem : highlightedCells) {
+ if (elem != null)
+ DOM.setStyleAttribute(elem, BACKGROUND, "#FFFFFF");
+ }
+ }
+
+ Date startDate =
+ ((AppointmentWidget) draggable.widget).getAppointment().getStart();
+ Date endDate =
+ ((AppointmentWidget) draggable.widget).getAppointment().getEnd();
+
+ int dateDiff = DateUtils.differenceInDays(endDate, startDate) + 1;
+ dateDiff = (dateDiff <= 0) ? 1 : dateDiff;
+ highlightedCells = getCells(row, col, dateDiff);
+
+ //TODO: month view highlighted cell style be moved to the css style sheet
+ for (Element elem : highlightedCells) {
+ if (elem != null) {
+ DOM.setStyleAttribute(elem, BACKGROUND, "#C3D9FF");
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback method executed once the drag has completed. We need to reset the
+ * background color of all previously highlighted cells. Also need to
+ * actually change the appointment's start / end date here (code doesn't
+ * exist yet).
+ *
+ * @param context An object containing information of what was dragged and
+ * dropped, including the Appointment,
+ * coordinates of the drop, etc.
+ */
+ @Override
+ public void onDrop(final DragContext context) {
+ super.onDrop(context);
+
+ for (Element elem : highlightedCells) {
+ if (elem != null) {
+ DOM.setStyleAttribute(elem, BACKGROUND, "#FFFFFF");
+ }
+ }
+ highlightedCells = null;
+
+ Draggable draggable = draggableList.get(0);
+
+ Appointment appointment =
+ ((AppointmentWidget) context.draggable).getAppointment();
+
+ long originalStartToEndTimeDistance =
+ appointment.getEnd().getTime() - appointment.getStart().getTime();
+
+ //get the column and row for the draggable widget
+ int row = getRow(context, draggable) - 1;
+ int col = getColumn(context, draggable);
+ int cell = row * daysPerWeek + col;
+
+ //calculate the new start & end dates
+ Date newStart = DateUtils.shiftDate(firstDateDisplayed, cell);
+ DateUtils.copyTime(appointment.getStart(), newStart);
+
+ Date newEnd = new Date(newStart.getTime() + originalStartToEndTimeDistance);
+
+ //Set the appointment's new start & end dates
+ appointment.setStart(newStart);
+ appointment.setEnd(newEnd);
+ }
+
+ /**
+ * Gets all the cells (as DOM Elements) that an appointment spans. Note: It
+ * only includes cells in the table. If an appointment ends in the following
+ * month the last cell in the list will be the last cell in the table.
+ *
+ * @param row Appointment's starting row
+ * @param col Appointment's starting column
+ * @param days Number of days an appointment spans
+ * @return Cell elements that an appointment spans
+ */
+ protected Element[] getCells(int row, int col, int days) {
+
+ Element[] elems = new Element[days];
+
+ for (int i = 0; i < days; i++) {
+ if (col > daysPerWeek - 1) {
+ col = 0;
+ row++;
+ }
+
+ /*
+ * Cheap code here. If the row / cell throw an out of index exception
+ * we just break. This kind of sucks because we have to
+ * now account for null items in the Element[] array.
+ */
+ try {
+ elems[i] = monthGrid.getFlexCellFormatter().getElement(row, col);
+ } catch (Exception ex) {
+ break;
+ }
+
+ col++;
+ }
+
+ return elems;
+ }
+
+ public int getRow(final DragContext context, final Draggable draggable) {
+ int y =
+ context.desiredDraggableY - dropTargetOffsetY + draggable.relativeY;
+ return (int)
+ Math.floor(y / (monthGrid.getOffsetHeight() / weeksPerMonth)) + 1;
+ }
+
+ public int getColumn(final DragContext context, final Draggable draggable) {
+ int x =
+ context.desiredDraggableX - dropTargetOffsetX + draggable.relativeX;
+ return (int)
+ Math.floor(x / (monthGrid.getOffsetWidth() / daysPerWeek));
+ }
+}
diff --git a/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewPickupDragController.java b/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewPickupDragController.java
new file mode 100644
index 0000000..0776089
--- /dev/null
+++ b/src/com/allen_sauer/gwt/dnd/client/drop/MonthViewPickupDragController.java
@@ -0,0 +1,19 @@
+package com.allen_sauer.gwt.dnd.client.drop;
+
+import com.allen_sauer.gwt.dnd.client.PickupDragController;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+
+public class MonthViewPickupDragController extends PickupDragController {
+
+ public MonthViewPickupDragController(AbsolutePanel boundaryPanel,
+ boolean allowDroppingOnBoundaryPanel) {
+ super(boundaryPanel, allowDroppingOnBoundaryPanel);
+ }
+
+ @Override
+ public void dragMove() {
+ try {
+ super.dragMove();
+ } catch(NullPointerException ex) { }
+ }
+}
diff --git a/src/com/biomed/Biomed.gwt.xml b/src/com/biomed/Biomed.gwt.xml
index b7ebf1a..d4bf386 100644
--- a/src/com/biomed/Biomed.gwt.xml
+++ b/src/com/biomed/Biomed.gwt.xml
@@ -43,4 +43,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/resources/BiomedTechViewStyleManager.java b/src/com/biomed/client/resources/BiomedTechViewStyleManager.java
new file mode 100644
index 0000000..79588a7
--- /dev/null
+++ b/src/com/biomed/client/resources/BiomedTechViewStyleManager.java
@@ -0,0 +1,18 @@
+package com.biomed.client.resources;
+
+import com.biomed.client.ui.schedule.BiomedAppointment;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.ThemeAppointmentStyle;
+import com.bradrydzewski.gwt.calendar.client.techview.TechViewStyleManager;
+
+public class BiomedTechViewStyleManager extends TechViewStyleManager {
+ @Override
+ protected ThemeAppointmentStyle getDefaultViewAppointmentStyleForTheme() {
+ return BiomedAppointmentTheme.GRAY;
+ }
+
+ @Override
+ protected ThemeAppointmentStyle getViewAppointmentStyleForTheme(Appointment appointment) {
+ return ((BiomedAppointment) appointment).getTheme();
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/SchedulePanel.java b/src/com/biomed/client/ui/schedule/SchedulePanel.java
index 8805209..506d5f0 100644
--- a/src/com/biomed/client/ui/schedule/SchedulePanel.java
+++ b/src/com/biomed/client/ui/schedule/SchedulePanel.java
@@ -2,15 +2,20 @@ package com.biomed.client.ui.schedule;
import java.util.Date;
import java.util.List;
+import java.util.Map;
import javax.inject.Inject;
import com.biomed.client.resources.BiomedAppointmentTheme;
+import com.biomed.client.ui.renderers.UserDTORenderer;
+import com.biomed.shared.api.dto.UserDTO;
import com.bradrydzewski.gwt.calendar.client.Appointment;
import com.bradrydzewski.gwt.calendar.client.Calendar;
import com.bradrydzewski.gwt.calendar.client.CalendarSettings;
import com.bradrydzewski.gwt.calendar.client.CalendarViews;
+import com.github.gwtbootstrap.client.ui.ValueListBox;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
@@ -20,6 +25,8 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasConstrainedValue;
+import com.google.gwt.user.datepicker.client.DateBox;
import com.google.maps.gwt.client.Geocoder;
import com.google.maps.gwt.client.Geocoder.Callback;
import com.google.maps.gwt.client.GeocoderRequest;
@@ -44,6 +51,12 @@ public class SchedulePanel extends Composite implements Schedule.View {
@UiField
FlowPanel mapContainer;
+ @UiField
+ DateBox datePicker;
+
+ @UiField(provided = true)
+ ValueListBox techPicker;
+
private static final LatLng center = LatLng.create(39.257147, -76.685686);
private Calendar calendar;
@@ -55,6 +68,7 @@ public class SchedulePanel extends Composite implements Schedule.View {
@Inject
public SchedulePanel(Binder binder) {
+ techPicker = new ValueListBox(new UserDTORenderer("All"));
initWidget(binder.createAndBindUi(this));
buildCalendar();
@@ -99,7 +113,7 @@ public class SchedulePanel extends Composite implements Schedule.View {
calendar = new Calendar();
calendar.setSettings(settings);
- calendar.setView(CalendarViews.DAY);
+ calendar.setView(CalendarViews.TECH);
calendar.setWidth("100%");
calendar.setHeight("100%");
calContainer.add(calendar);
@@ -115,8 +129,11 @@ public class SchedulePanel extends Composite implements Schedule.View {
}
markers.clear();
+ int columnId = -1;
+ Map columnMap = Maps.newHashMap();
+
for (Workorder w : workorders) {
- String address = null;
+ String address = null;
if (w.clientAddress != null) {
address = w.clientAddress + "\n" + w.clientCity + ", " + w.clientState + ". " + w.clientZip;
}
@@ -132,6 +149,14 @@ public class SchedulePanel extends Composite implements Schedule.View {
apt.setStart(w.jobStart);
apt.setEnd(w.jobEnd);
apt.setTheme(BiomedAppointmentTheme.STYLES.get(w.color));
+
+ if (columnMap.containsKey(w.techId)) {
+ apt.setColumnId(columnMap.get(w.techId));
+ } else {
+ columnId++;
+ columnMap.put(w.techId, columnId);
+ apt.setColumnId(columnId);
+ }
calendar.addAppointment(apt);
appointments.add(apt);
@@ -178,4 +203,8 @@ public class SchedulePanel extends Composite implements Schedule.View {
geocoder = Geocoder.create();
}
+
+ public HasConstrainedValue getTechField() {
+ return techPicker;
+ }
}
diff --git a/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml b/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml
index 1445011..45c05a2 100644
--- a/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml
+++ b/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml
@@ -1,9 +1,21 @@
-
+ xmlns:b="urn:import:com.github.gwtbootstrap.client.ui"
+ xmlns:dp="urn:import:com.google.gwt.user.datepicker.client">
+
+ New Workorder
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/biomed/server/BiomedModule.java b/src/com/biomed/server/BiomedModule.java
index 19ec8b9..cf3a963 100644
--- a/src/com/biomed/server/BiomedModule.java
+++ b/src/com/biomed/server/BiomedModule.java
@@ -32,7 +32,8 @@ public class BiomedModule extends AbstractModule {
private void bindProperties() {
Properties prop = new Properties();
try {
- prop.load(new FileInputStream("/srv/biomed/server.properties"));
+ System.out.println("Current Directory: " + System.getProperty("user.dir"));
+ prop.load(new FileInputStream("../server.properties"));
Names.bindProperties(binder(), prop);
} catch (IOException e) {
e.printStackTrace();
diff --git a/src/com/biomed/server/CalendarApi.java b/src/com/biomed/server/CalendarApi.java
index f6f3d37..766596f 100644
--- a/src/com/biomed/server/CalendarApi.java
+++ b/src/com/biomed/server/CalendarApi.java
@@ -7,6 +7,7 @@ import java.util.Map;
import java.util.TimeZone;
import javax.inject.Inject;
+import javax.inject.Named;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
@@ -27,121 +28,143 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class CalendarApi {
- private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
- private static final JsonFactory JSON_FACTORY = new JacksonFactory();
- private static final String CLIENT_ID = "333768673996-904fuljpb428q9r37m2uujislhal5kt9.apps.googleusercontent.com";
- private static final String CLIENT_SECRET = "PAmHruqVbAXzEnnTwelx-Ll7";
- private static final String REFRESH_TOKEN = "1/fWvZebnDAhY0MlgK1IImcrGIDm4ZlILiRM8_47HsUFc";
+ private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
+ private static final JsonFactory JSON_FACTORY = new JacksonFactory();
+ private static final String CLIENT_ID = "333768673996-904fuljpb428q9r37m2uujislhal5kt9.apps.googleusercontent.com";
+ private static final String CLIENT_SECRET = "PAmHruqVbAXzEnnTwelx-Ll7";
+ private static final String REFRESH_TOKEN = "1/fWvZebnDAhY0MlgK1IImcrGIDm4ZlILiRM8_47HsUFc";
- @Inject
- public CalendarApi() {
- }
+ private final boolean devmode;
- public String scheduleEvent(String email, Date startDate, Date endDate, String summary, String description, String location) {
- EventAttendee attendee = new EventAttendee();
- attendee.setEmail(email);
+ @Inject
+ public CalendarApi(@Named("devmode") boolean devmode) {
+ this.devmode = devmode;
+ }
- Event event = new Event();
- event.setSummary(summary);
- event.setLocation(location);
- event.setDescription(description);
+ public String scheduleEvent(String email, Date startDate, Date endDate,
+ String summary, String description, String location) {
+ if (devmode) {
+ return null;
+ }
- List attendees = Lists.newArrayList();
- attendees.add(attendee);
- event.setAttendees(attendees);
+ EventAttendee attendee = new EventAttendee();
+ attendee.setEmail(email);
- DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
- event.setStart(new EventDateTime().setDateTime(start));
- DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
- event.setEnd(new EventDateTime().setDateTime(end));
+ Event event = new Event();
+ event.setSummary(summary);
+ event.setLocation(location);
+ event.setDescription(description);
- Calendar calendar = buildCalendar();
- try {
- Event resultEvent = calendar.events().insert("api@atlanticbiomedical.com", event).execute();
- return resultEvent.getId();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ List attendees = Lists.newArrayList();
+ attendees.add(attendee);
+ event.setAttendees(attendees);
- public void rescheduleEvent(String eventId, String email, Date startDate, Date endDate) {
- Calendar calendar = buildCalendar();
+ DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
+ event.setStart(new EventDateTime().setDateTime(start));
+ DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
+ event.setEnd(new EventDateTime().setDateTime(end));
- try {
- Event event = calendar.events().get("api@atlanticbiomedical.com", eventId).execute();
+ Calendar calendar = buildCalendar();
+ try {
+ Event resultEvent = calendar.events()
+ .insert("api@atlanticbiomedical.com", event).execute();
+ return resultEvent.getId();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
- EventAttendee attendee = new EventAttendee();
- attendee.setEmail(email);
- List attendees = event.getAttendees();
- attendees.clear();
- attendees.add(attendee);
+ public void rescheduleEvent(String eventId, String email, Date startDate,
+ Date endDate) {
+ if (devmode) {
+ return;
+ }
- DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
- event.setStart(new EventDateTime().setDateTime(start));
- DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
- event.setEnd(new EventDateTime().setDateTime(end));
+ Calendar calendar = buildCalendar();
- calendar.events().update("api@atlanticbiomedical.com", eventId, event).execute();
- }
- catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ try {
+ Event event = calendar.events()
+ .get("api@atlanticbiomedical.com", eventId).execute();
- public void deleteEvent(String eventId) {
- Calendar calendar = buildCalendar();
+ EventAttendee attendee = new EventAttendee();
+ attendee.setEmail(email);
+ List attendees = event.getAttendees();
+ attendees.clear();
+ attendees.add(attendee);
- try {
- calendar.events().delete("api@atlanticbiomedical.com", eventId).execute();
- }
- catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
+ event.setStart(new EventDateTime().setDateTime(start));
+ DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
+ event.setEnd(new EventDateTime().setDateTime(end));
- private static Map buildCalendarMap() {
- Map result = Maps.newHashMap();
+ calendar.events().update("api@atlanticbiomedical.com", eventId, event)
+ .execute();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
- try {
- Calendar calendar = buildCalendar();
- CalendarList feed = calendar.calendarList().list().execute();
- for (CalendarListEntry entry : feed.getItems()) {
- String summary = entry.getSummary();
- if (summary != null && summary.endsWith("atlanticbiomedical.com")) {
- result.put(summary, entry.getId());
- }
+ public void deleteEvent(String eventId) {
+ if (devmode) {
+ return;
+ }
- System.out.println("Found Calendar: " + summary + " -> " + entry.getId());
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ Calendar calendar = buildCalendar();
- return result;
- }
+ try {
+ calendar.events().delete("api@atlanticbiomedical.com", eventId).execute();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
- public void test() throws IOException {
- Calendar calendar = buildCalendar();
- CalendarList feed = calendar.calendarList().list().execute();
- for (CalendarListEntry entry : feed.getItems()) {
- System.out.println(entry.getId());
- }
- }
+ private static Map buildCalendarMap() {
+ Map result = Maps.newHashMap();
- private static Calendar buildCalendar() {
- try {
- GoogleCredential credential = new GoogleCredential().setAccessToken(getAccessToken());
- Calendar calendar = Calendar.builder(HTTP_TRANSPORT, JSON_FACTORY)
- .setApplicationName("BiomedPortal/1.0").setHttpRequestInitializer(credential).build();
- return calendar;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
+ try {
+ Calendar calendar = buildCalendar();
+ CalendarList feed = calendar.calendarList().list().execute();
+ for (CalendarListEntry entry : feed.getItems()) {
+ String summary = entry.getSummary();
+ if (summary != null && summary.endsWith("atlanticbiomedical.com")) {
+ result.put(summary, entry.getId());
+ }
- private static String getAccessToken() throws TokenResponseException, IOException {
- TokenResponse response = new GoogleRefreshTokenRequest(HTTP_TRANSPORT, JSON_FACTORY,
- REFRESH_TOKEN, CLIENT_ID, CLIENT_SECRET).execute();
- return response.getAccessToken();
- }
+ System.out.println("Found Calendar: " + summary + " -> "
+ + entry.getId());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return result;
+ }
+
+ public void test() throws IOException {
+ Calendar calendar = buildCalendar();
+ CalendarList feed = calendar.calendarList().list().execute();
+ for (CalendarListEntry entry : feed.getItems()) {
+ System.out.println(entry.getId());
+ }
+ }
+
+ private static Calendar buildCalendar() {
+ try {
+ GoogleCredential credential = new GoogleCredential()
+ .setAccessToken(getAccessToken());
+ Calendar calendar = Calendar.builder(HTTP_TRANSPORT, JSON_FACTORY)
+ .setApplicationName("BiomedPortal/1.0")
+ .setHttpRequestInitializer(credential).build();
+ return calendar;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String getAccessToken() throws TokenResponseException,
+ IOException {
+ TokenResponse response = new GoogleRefreshTokenRequest(HTTP_TRANSPORT,
+ JSON_FACTORY, REFRESH_TOKEN, CLIENT_ID, CLIENT_SECRET).execute();
+ return response.getAccessToken();
+ }
}
diff --git a/src/com/bradrydzewski/gwt/calendar/Calendar.gwt.xml b/src/com/bradrydzewski/gwt/calendar/Calendar.gwt.xml
new file mode 100644
index 0000000..b5ec24b
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/Calendar.gwt.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/Appointment.java b/src/com/bradrydzewski/gwt/calendar/client/Appointment.java
new file mode 100644
index 0000000..0ab0c38
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/Appointment.java
@@ -0,0 +1,455 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010-2011 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * The Appointment class provides a set of text-based properties to
+ * describe it, including a title, description, location, createdBy,
+ * etc. Additional to these, there is a set of properties that exist to provide
+ * the gwt-cal components with information useful during the
+ * Appointment rendering in the widget views (allDay,
+ * recurring, etc.)
+ *
+ *
+ * All Appointment properties are ultimately used by the gwt-cal
+ * views and it is up to these components to decide how to render (if at all)
+ * them as well as to provide appropriate runtime features to modify them.
+ *
+ * Creates an Appointment with the following attributes set to
+ * null
+ *
+ *
+ *
title
+ *
description
+ *
start
+ *
end
+ *
location
+ *
createdBy
+ *
+ * the attendees collection empty the allDay and
+ * the readOnly property false.
+ *
+ *
+ */
+ public Appointment() {
+
+ }
+
+ /**
+ * Returns the unique identifier for this Appointment. The
+ * field is optional (and not used by gwt-cal) and therefore may be null.
+ *
+ * @return A unique identifier for this Appointment (optional).
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of this Appointment. This
+ * identifier is optional.
+ *
+ * @param id Arbitrary string to uniquely identify the appointment.
+ */
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the configured start time-stamp of this Appointment.
+ *
+ * @return A date object with the date and time this appointment starts on
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * Sets the start time-stamp of this Appointment.
+ *
+ * @param start
+ * A date object with the date and time this appointment starts
+ */
+ public void setStart(final Date start) {
+ this.start = start;
+ }
+
+ /**
+ * Returns the configured end time-stamp of this Appointment.
+ *
+ * @return A date object with the date and time this appointment ends on
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * Sets the end time-stamp of this Appointment.
+ *
+ * @param end
+ * A date object with the date and time this appointment ends on
+ */
+ public void setEnd(final Date end) {
+ this.end = end;
+ }
+
+ /**
+ * Returns the identifying title of this Appointment.
+ *
+ * @return The title's short text
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the identifying title of this Appointment.
+ *
+ * @param title
+ * The title's short text
+ */
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ /**
+ * Returns a description for this Appointment.
+ *
+ * @return The appointment's description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the description of this Appointment.
+ *
+ * @param description
+ * The title's short text
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * Returns a location of this Appointment.
+ *
+ * @return The appointment location.
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ /**
+ * Sets the location of this Appointment.
+ *
+ * @param location
+ * The appointment location
+ */
+ public void setLocation(final String location) {
+ this.location = location;
+ }
+
+ /**
+ * Returns a creator of this Appointment.
+ *
+ * @return The appointment creator description.
+ */
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ /**
+ * Sets the creator of this Appointment.
+ *
+ * @param createdBy
+ * The appointment creator description.
+ */
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ /**
+ * Returns the collection of associated attendees.
+ *
+ * @return The currently configured list of attendees
+ */
+ public List getAttendees() {
+ return attendees;
+ }
+
+ /**
+ * Sets the attendees associated to this Appointment.
+ *
+ * @param attendees
+ * The entities associated (attending) this
+ * Appointment
+ */
+ public void setAttendees(final List attendees) {
+ this.attendees = attendees;
+ }
+
+ /**
+ * Compares this Appointment with the specified
+ * appointment based first on the start dates of
+ * each appointment and then (if they happen to be the same), on the
+ * end dates.
+ *
+ * @param appointment
+ * The appointment to compare this one to
+ * @return a negative integer if this appointment
+ * is beforeappointment, zero if
+ * both appointments have the same start/
+ * end dates, and a positive integer if
+ * this appointment is after
+ * appointment.
+ */
+ public int compareTo(final Appointment appointment) {
+ int compare = this.getStart().compareTo(appointment.getStart());
+
+ if (compare == 0) {
+ compare = appointment.getEnd().compareTo(this.getEnd());
+ }
+
+ return compare;
+ }
+
+ /**
+ * Tells whether this Appointment spans more than a single day,
+ * based on its start and end properties.
+ *
+ * @return true if the start and end
+ * dates fall on different dates, false otherwise.
+ */
+ public boolean isMultiDay() {
+ if (getEnd() != null && getStart() != null) {
+ return !DateUtils.areOnTheSameDay(getEnd(), getStart());
+ }
+ throw new IllegalStateException(
+ "Calculating isMultiDay with no start/end dates set");
+ }
+
+ /**
+ * Returns the configured value of the allDay property, which
+ * indicates if this Appointment should be considered as
+ * spanning all day. It is left to the view rendering this
+ * Appointment to decide how to render an appointment based on
+ * this property value. For instance, the month view, will display the
+ * Appointment at the top of the days in a week.
+ *
+ * @return The current value of the allDay property
+ */
+ public boolean isAllDay() {
+ return allDay;
+ }
+
+ /**
+ * Configures the the allDay property, which indicates if this
+ * Appointment should be considered as spanning all day. It is
+ * left to the view rendering this Appointment to decide how to
+ * render an appointment based on this property value. For instance, the
+ * month view, will display the Appointment at the top of the
+ * days in a week.
+ *
+ * @param allDay
+ * The current value of the allDay property
+ */
+ public void setAllDay(final boolean allDay) {
+ this.allDay = allDay;
+ }
+
+ public AppointmentStyle getStyle() {
+ return style;
+ }
+
+ public void setStyle(final AppointmentStyle style) {
+ this.style = style;
+ }
+
+ public String getCustomStyle() {
+ return customStyle;
+ }
+
+ public void setCustomStyle(final String customStyle) {
+ this.customStyle = customStyle;
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public void setReadOnly(final boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ public Appointment clone() {
+ Appointment clone = new Appointment();
+ clone.setId(this.id);
+ clone.setAllDay(this.allDay);
+ clone.setAttendees(new ArrayList(this.attendees));
+ clone.setCreatedBy(this.createdBy);
+ clone.setDescription(this.description);
+ clone.setEnd(DateUtils.newDate(this.end));
+ clone.setLocation(this.location);
+ clone.setStart(DateUtils.newDate(this.start));
+ clone.setTitle(this.title);
+ clone.setStyle(this.style);
+ clone.setCustomStyle(this.customStyle);
+ clone.setReadOnly(this.readOnly);
+
+ return clone;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + Boolean.valueOf(this.allDay).hashCode();
+ result = prime * result + ((attendees == null) ? 0 : attendees.hashCode());
+ result = prime * result + ((createdBy == null) ? 0 : createdBy.hashCode());
+ result = prime * result + ((description == null) ? 0 : description.hashCode());
+ result = prime * result + ((end == null) ? 0 : end.hashCode());
+ result = prime * result + ((start == null) ? 0 : start.hashCode());
+ result = prime * result + ((location == null) ? 0 : location.hashCode());
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ result = prime * result + Boolean.valueOf(this.readOnly).hashCode();
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Appointment)) {
+ return false;
+ }
+ Appointment other = (Appointment) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ if (description == null) {
+ if (other.description != null) {
+ return false;
+ }
+ } else if (!description.equals(other.description)) {
+ return false;
+ }
+ if (allDay != other.allDay) {
+ return false;
+ }
+ if (attendees == null) {
+ if (other.attendees != null) {
+ return false;
+ }
+ } else if (!attendees.equals(other.attendees)) {
+ return false;
+ }
+ if (createdBy == null) {
+ if (other.createdBy != null) {
+ return false;
+ }
+ } else if (!createdBy.equals(other.createdBy)) {
+ return false;
+ }
+ if (end == null) {
+ if (other.end != null) {
+ return false;
+ }
+ } else if (!end.equals(other.end)) {
+ return false;
+ }
+ if (start == null) {
+ if (other.start != null) {
+ return false;
+ }
+ } else if (!start.equals(other.start)) {
+ return false;
+ }
+ if (location == null) {
+ if (other.location != null) {
+ return false;
+ }
+ } else if (!location.equals(other.location)) {
+ return false;
+ }
+ if (title == null) {
+ if (other.title != null) {
+ return false;
+ }
+ } else if (!title.equals(other.title)) {
+ return false;
+ }
+ if (readOnly != other.readOnly) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/AppointmentManager.java b/src/com/bradrydzewski/gwt/calendar/client/AppointmentManager.java
new file mode 100644
index 0000000..4696663
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/AppointmentManager.java
@@ -0,0 +1,355 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ * The key responsibilities of the AppointmentManager are:
+ *
Keep the calendar collection of appointments and provide operations to
+ * update it
Identify one of the appointments as the "currently
+ * selected"
Provide "navigation" methods to move the
+ * "currently selected" from appointment to appointment over the
+ * collection
Keep track of changes to the set of appointments to
+ * identify when sorting of the appointments -if client code needs them
+ * chronologically ordered- should take place, i.e. appointments are not
+ * guaranteed to be always in chronological order
+ *
+ * @author Brad Rydzewski
+ * @author Carlos D. Morales
+ */
+public class AppointmentManager {
+ /**
+ * A reference to the "currently selected appointment". Will be
+ * null when no currently selected appointment has been set.
+ */
+ private Appointment selectedAppointment = null;
+
+ /**
+ * A reference to the most recent appointment to receive
+ * a mouse over event.
+ */
+ private Appointment hoveredAppointment = null;
+
+ /**
+ * A copy of the last appointment that was updated,
+ * prior to the update taking place.
+ */
+ private Appointment rollbackAppointment = null;
+
+ /**
+ * A reference to the last appointment that was updated.
+ */
+ private Appointment committedAppointment = null;
+
+ /**
+ * The collection of appointments to be maintained by this
+ * AppointmentManager.
+ */
+ private ArrayList appointments = new ArrayList();
+
+ /**
+ * Internal state flag indicating whether the collection of appointments
+ * needs to be sorted.
+ */
+ private boolean sortPending = true;
+
+ /**
+ * Returns the collection of appointments that this AppointmentManager
+ * maintains. Warning: this method returns a modifiable
+ * reference to the internally managed list of appointments; client code
+ * might break the invariants that this AppointmentManager
+ * enforces if it performs any operations that modify the returned
+ * collection.
+ *
+ * @return The appointments managed by this AppointmentManager.
+ */
+ public List getAppointments() {
+ return appointments;
+ }
+
+ /**
+ * Adds an appointment to the collection of appointments maintained by this
+ * ApplicationManager.
+ *
+ * @param appt The appointment to be made part of this manager's managed
+ * collection
+ */
+ public void addAppointment(Appointment appt) {
+ if (appt != null) {
+ appointments.add(appt);
+ sortPending = true;
+ }
+ }
+
+ /**
+ * Adds multiple appointments to the collection maintained by this
+ * ApplicationManager.
+ *
+ * @param appointments The appointments that will be made part of this
+ * manager's managed collection
+ */
+ public void addAppointments(List appointments) {
+ if (appointments != null) {
+ for (Appointment appointment : appointments) {
+ addAppointment(appointment);
+ }
+ }
+ }
+
+ /**
+ * Removes the appointment from this manager's managed
+ * collection.
+ *
+ * @param appointment The appointment to remove
+ */
+ public void removeAppointment(Appointment appointment) {
+ if (appointment != null) {
+ boolean wasRemoved = appointments.remove(appointment);
+ if (wasRemoved) {
+ sortPending = true;
+ }
+ //I'd rather have the client keep the reference to the selected one if they need it than having here
+ //a reference to a thing I no longer should know about...
+ if (hasAppointmentSelected() &&
+ getSelectedAppointment().equals(appointment)) {
+ selectedAppointment = null;
+ }
+ }
+ }
+
+ /**
+ * Removes the "currently selected" appointment from this
+ * manager's collection.
+ */
+ public void removeCurrentlySelectedAppointment() {
+ if (hasAppointmentSelected()) {
+ removeAppointment(getSelectedAppointment());
+ selectedAppointment = null;
+ }
+ }
+
+ /**
+ * Empties the collection of managed appointments.
+ */
+ public void clearAppointments() {
+ appointments.clear();
+ }
+
+ /**
+ * Sets the appointment that should be considered the "currently
+ * selected" appointment.
+ *
+ * @param selectedAppointment The appointment to consider "currently
+ * selected"
+ */
+ public void setSelectedAppointment(Appointment selectedAppointment) {
+ if (selectedAppointment != null &&
+ appointments.contains(selectedAppointment)) {
+ this.selectedAppointment = selectedAppointment;
+ }
+ }
+
+ /**
+ * Indicates whether there is a "currently selected" appointment
+ * at the moment.
+ *
+ * @return true if there is a currently selected appointment
+ * for the collection managed by this component, false
+ * otherwise
+ */
+ public boolean hasAppointmentSelected() {
+ return selectedAppointment != null;
+ }
+
+ /**
+ * Returns the appointment in this manager's collection that is
+ * "currently selected".
+ *
+ * @return The currently selected appointment
+ */
+ public Appointment getSelectedAppointment() {
+ return selectedAppointment;
+ }
+
+ /**
+ * Sorts the collection of appointments by their natural order.
+ */
+ public void sortAppointments() {
+ if (sortPending) {
+ Collections.sort(appointments);
+ sortPending = false;
+ }
+ }
+
+ /**
+ * Moves the "currently selected" to the previous appointment in
+ * the managed collection of this AppointmentManager. The
+ * "previous" appointment will be the appointment before the
+ * currently selected appointment in the set of appointments (whether
+ * ordered or not).
+ *
+ * Because this operation depends on a "currently selected
+ * appointment", no previous appointment is considered to exist if
+ * there is no "currently selected appointment." or it is the
+ * first in the set.
+ *
+ * @return true if selecting the previous appointment was
+ * successful, false no currently selected appointment
+ * is set or the currently selected appointment is the first in the
+ * collection.
+ */
+ public boolean selectPreviousAppointment() {
+ boolean moveSucceeded = false;
+ if (getSelectedAppointment() != null) {
+ int selectedApptIndex =
+ getAppointments().indexOf(getSelectedAppointment());
+ Appointment prevAppt;
+ if (selectedApptIndex > 0 && (prevAppt =
+ getAppointments().get(selectedApptIndex - 1)) != null) {
+ selectedAppointment = prevAppt;
+ moveSucceeded = true;
+ }
+ }
+ return moveSucceeded;
+ }
+
+ /**
+ * Moves the "currently selected" to the next appointment in the
+ * managed collection of this AppointmentManager. The
+ * "next" appointment will be the appointment after the currently
+ * selected appointment in the set of appointments (whether ordered or
+ * not).
+ *
+ * Because this operation depends on a "currently selected
+ * appointment", no next appointment is considered to exist if there is
+ * no "currently selected appointment or it is the last in the
+ * set."
+ *
+ * @return true if selecting the previous appointment was
+ * successful, false no currently selected appointment
+ * is set or the currently selected appointment is the last in the
+ * collection.
+ */
+ public boolean selectNextAppointment() {
+ boolean moveSucceeded = false;
+
+ if (getSelectedAppointment() != null) {
+ int selectedApptIndex =
+ getAppointments().indexOf(getSelectedAppointment());
+ int lastIndex = getAppointments().size() - 1;
+
+ Appointment nextAppt;
+ if (selectedApptIndex < lastIndex
+ && (nextAppt = getAppointments().get(selectedApptIndex + 1)) != null) {
+ selectedAppointment = nextAppt;
+ moveSucceeded = true;
+ }
+ }
+
+ return moveSucceeded;
+ }
+
+ /**
+ * Resets the "currently selected" appointment of this manager.
+ * If this manager has a currently selected appointment, the
+ * appointment selected property will be set to
+ * false and this manager's selectedAppointment
+ * property will be set to null.
+ */
+ public void resetSelectedAppointment() {
+ if (hasAppointmentSelected()) {
+ //selectedAppointment.setSelected(false);
+ selectedAppointment = null;
+ }
+ }
+
+ /**
+ * Tells whether the passed appointment is the same one as the
+ * one that is currently selected in this manager.
+ *
+ * @param appointment The appointment to test to be the same as the
+ * currently selected
+ * @return true if there is a currently selected appointment
+ * and it is equal to the passed appointment
+ */
+ public boolean isTheSelectedAppointment(Appointment appointment) {
+ return hasAppointmentSelected() && selectedAppointment.equals(
+ appointment);
+ }
+
+ public void commit() {
+ rollbackAppointment = null;
+ committedAppointment = null;
+ }
+
+ public void rollback() {
+
+ //if the snapshot block is empty, we can do nothing
+ if(rollbackAppointment==null && committedAppointment==null)
+ return;
+
+ //if there is no committed appointment, we assume
+ // this was a delete operation. We re-add the appointment
+ if (committedAppointment == null) {
+ addAppointment(rollbackAppointment);
+
+ //if there is no rollback appointment, we assume
+ // this was an add or update operation. We remove the appointment
+ } else if (rollbackAppointment == null) {
+ removeAppointment(committedAppointment);
+
+ //else, we assume this is an update
+ } else {
+
+ removeAppointment(committedAppointment);
+ addAppointment(rollbackAppointment);
+ }
+
+ commit();
+ }
+
+ public void setRollbackAppointment(Appointment appt) {
+ sortPending = true;
+ commit();
+ rollbackAppointment = appt;
+ }
+
+ public void setCommittedAppointment(Appointment appt) {
+ sortPending = true;
+ committedAppointment = appt;
+ }
+
+ public void resetHoveredAppointment() {
+ this.hoveredAppointment = null;
+ }
+
+ public void setHoveredAppointment(Appointment hoveredAppointment) {
+ this.hoveredAppointment = hoveredAppointment;
+ }
+
+ public Appointment getHoveredAppointment() {
+ return hoveredAppointment;
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/AppointmentStyle.java b/src/com/bradrydzewski/gwt/calendar/client/AppointmentStyle.java
new file mode 100644
index 0000000..dd6ff08
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/AppointmentStyle.java
@@ -0,0 +1,12 @@
+package com.bradrydzewski.gwt.calendar.client;
+
+import java.io.Serializable;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+public enum AppointmentStyle implements Serializable, IsSerializable {
+ BLUE, RED, PINK, PURPLE, DARK_PURPLE, STEELE_BLUE, LIGHT_BLUE,
+ TEAL, LIGHT_TEAL, GREEN, LIGHT_GREEN, YELLOW_GREEN, YELLOW,
+ ORANGE, RED_ORANGE, LIGHT_BROWN, LIGHT_PURPLE, GREY, BLUE_GREY,
+ YELLOW_GREY, BROWN, DEFAULT, CUSTOM
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/Attendee.java b/src/com/bradrydzewski/gwt/calendar/client/Attendee.java
new file mode 100644
index 0000000..7cd0ddb
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/Attendee.java
@@ -0,0 +1,150 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see Attendee id. This field can be used to relate the Attendee with some
+ * external source (contact, resource, ....)
+ */
+ private String id;
+
+ /**
+ * The Attendee name (if a person) or description
+ * (when a resource).
+ */
+ private String name;
+
+ /**
+ * This Attendee email address.
+ */
+ private String email;
+
+ /**
+ * The status of attendance of this attendee to an Appointment.
+ */
+ private Attending attending = Attending.Maybe;
+
+ /**
+ * A URL to an image to depict this Attendee.
+ */
+ private String imageUrl;
+
+ /**
+ * Returns the name (if a person) or description (when a resource)
+ * of this Attendee.
+ *
+ * @return The currently configured name of this attendee
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name (if a person) or description (when a resource)
+ * of this Attendee.
+ *
+ * @param name The name of this attendee
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns this Attendee email address.
+ *
+ * @return The email address
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets this Attendee email address.
+ * @param email The email address
+ */
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ /**
+ * Returns the attendance status of this attendant
+ * to the Appointment referencing it.
+ * @return The attendance status of this Attendee
+ */
+ public Attending getAttending() {
+ return attending;
+ }
+
+ /**
+ * Sets the attendance status of this attendant
+ * to the Appointment referencing it.
+ *
+ * @param attending The attendance status
+ */
+ public void setAttending(Attending attending) {
+ this.attending = attending;
+ }
+
+ /**
+ * Returns the URL to an image to (optionally) depict this Attendee
+ * in the views.
+ * @return A URL (relative or absolute) meaningful within the
+ * deployed application context
+ */
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ /**
+ * Sets the URL to an image to (optionally) depict this Attendee
+ * in the views.
+ * @param imageUrl A URL (relative or absolute) meaningful within the
+ */
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+
+ /**
+ * Returns the attendance ID.
+ * @return The attendance ID
+ * @since 0.9.4
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the attendance the ID.
+ * @param id The ID
+ * @since 0.9.4
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/Attending.java b/src/com/bradrydzewski/gwt/calendar/client/Attending.java
new file mode 100644
index 0000000..0d3f5b4
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/Attending.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see Calendar with the DayView currently displayed.
+ */
+ public Calendar() {
+ this(CalendarViews.DAY, CalendarSettings.DEFAULT_SETTINGS);
+ }
+
+ public Calendar(CalendarSettings settings) {
+ this(CalendarViews.DAY, settings);
+ }
+
+ /**
+ * Constructs a Calendar with the given CalendarView displayed by
+ * default.
+ */
+ public Calendar(CalendarViews view, CalendarSettings settings) {
+ super();
+ this.setSettings(settings);
+ setView(view);
+ }
+
+ /**
+ * Constructs a Calendar with the a user-defined CalendarView
+ * displayed by default.
+ */
+ public Calendar(CalendarView view) {
+ super();
+ setView(view);
+ }
+
+ /**
+ * Sets the CalendarView that should be used by the Calendar to display the
+ * list of appointments.
+ *
+ * @param view
+ */
+ final public void setView(CalendarViews view) {
+ setView(view, getDays());
+ }
+
+ /**
+ * Sets the current view of this calendar.
+ *
+ * @param view
+ * The ID of a view used to visualize the appointments managed by the
+ * calendar
+ * @param days
+ * The number of days to display in the view, which can be ignored by
+ * some views.
+ */
+ public void setView(CalendarViews view, int days) {
+ switch (view) {
+ case DAY: {
+ if (dayView == null) {
+ dayView = new DayView();
+ }
+ dayView.setDisplayedDays(days);
+ setView(dayView);
+ break;
+ }
+ case AGENDA: {
+ // TODO: need to cache agendaView, but there is a layout bug after a
+ // calendar item is deleted.
+ // agendaView = new AgendaView();
+ // setView(agendaView);
+ // break;
+ throw new RuntimeException("Agenda View is not yet supported");
+ }
+ case MONTH: {
+ if (monthView == null) {
+ monthView = new MonthView();
+ }
+ setView(monthView);
+ break;
+ }
+ case TECH: {
+ if (techView == null) {
+ techView = new TechView();
+ }
+ setView(techView);
+ break;
+ }
+ }
+ selectedView = view;
+ }
+
+ /**
+ * Gets the current view of this calendar.
+ *
+ * @return Current view
+ */
+ public CalendarViews getCalendarView() {
+ return selectedView;
+ }
+
+ public void onResize() {
+ resizeTimer.schedule(500);
+ }
+
+ private Timer resizeTimer = new Timer() {
+ /**
+ * Snapshot of the Calendar's height at the last time it was resized.
+ */
+ private int height;
+
+ @Override
+ public void run() {
+
+ int newHeight = getOffsetHeight();
+ if (newHeight != height) {
+ height = newHeight;
+ doSizing();
+ if (getView() instanceof MonthView)
+ doLayout();
+ }
+ }
+ };
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/CalendarFormat.java b/src/com/bradrydzewski/gwt/calendar/client/CalendarFormat.java
new file mode 100644
index 0000000..0054afa
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/CalendarFormat.java
@@ -0,0 +1,312 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009-2011 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see DateTimeFormat.
+ *
+ * @param formatPattern The pattern to format day names
+ * @see com.google.gwt.i18n.client.DateTimeFormat#getFormat(String)
+ */
+ public void setDayOfWeekFormat(String formatPattern) {
+ dayOfWeekFormat = DateTimeFormat.getFormat(formatPattern);
+ refreshWeekDayNames();
+ }
+
+ /**
+ * Returns the names (labels) of the days of the week.
+ *
+ * @return The days of the 7 days of the week, formatted with the current
+ * configuration
+ */
+ public String[] getDayOfWeekNames() {
+ return weekDayNames;
+ }
+
+ /**
+ * Configures the formatting pattern to render the days of the week in an
+ * abbreviated manner using DateTimeFormat.
+ *
+ * @param formatPattern The pattern to format day names
+ * @see com.google.gwt.i18n.client.DateTimeFormat#getFormat(String)
+ */
+ public void setDayOfWeekAbbreviatedFormat(String formatPattern) {
+ dayOfWeekAbbreviatedFormat = DateTimeFormat.getFormat(formatPattern);
+ refreshWeekDayNames();
+ }
+
+ public String[] getDayOfWeekAbbreviatedNames() {
+ return dayOfWeekAbbreviatedNames;
+ }
+
+ private void refreshWeekDayNames() {
+ Date date = new Date();
+ for (int i = 1; i <= 7; i++) {
+ date.setDate(i);
+ int dayOfWeek = date.getDay();
+ weekDayNames[dayOfWeek] = dayOfWeekFormat.format(date);
+ dayOfWeekAbbreviatedNames[dayOfWeek] =
+ dayOfWeekAbbreviatedFormat.format(date);
+ }
+ }
+
+ /**
+ * Configures the formatting pattern to render the days of the month using
+ * DateTimeFormat. Most likely, formatPattern will
+ * contain at the minimum, the format to render the number of the
+ * corresponding day.
+ *
+ * @param formatPattern The pattern to format days in the month view
+ * @see com.google.gwt.i18n.client.DateTimeFormat#getFormat(String)
+ */
+ public void setDayOfMonthFormat(String formatPattern) {
+ dayOfMonthFormat = DateTimeFormat.getFormat(formatPattern);
+ refreshMonthDayNames();
+ }
+
+ private void refreshMonthDayNames() {
+ Date date = new Date();
+ date.setMonth(0);
+ for (int i = 1; i < 32; ++i) {
+ date.setDate(i);
+ dayOfMonthNames[i] = dayOfMonthFormat.format(date);
+ }
+ }
+
+ public void setDateFormat(String formatPattern) {
+ dateFormat = DateTimeFormat.getFormat(formatPattern);
+ }
+
+ public DateTimeFormat getDateFormat() {
+ return dateFormat;
+ }
+
+ /**
+ * Sets the pattern used to format the displayed hours and re-generates
+ * all hour labels.
+ *
+ * @param formatPattern A legal format following the patterns
+ * in {@link com.google.gwt.i18n.client.DateTimeFormat}
+ */
+ public void setTimeFormat(String formatPattern) {
+ timeFormat = DateTimeFormat.getFormat(formatPattern);
+ generateHourLabels();
+ }
+
+ public DateTimeFormat getTimeFormat() {
+ return timeFormat;
+ }
+
+ /**
+ * Allows programmatic configuration of the 24 hour labels in the calendar.
+ *
+ * @param hourLabels The labels to be used as labels for the hours of the
+ * day.
+ * @throws IllegalArgumentException If the hourLabels array is
+ * null, does not have 24
+ * elements, or any of the elements is
+ * null
+ */
+ public void setHourLabels(String[] hourLabels) {
+ if (hourLabels == null || hourLabels.length != HOURS_IN_DAY) {
+ throw new IllegalArgumentException(
+ "24 Hour labels expected. Please provide an array with 24 non-null values");
+ }
+ for (int i = 0; i < HOURS_IN_DAY; i++) {
+ if (hourLabels[i] == null) {
+ throw new IllegalArgumentException(
+ "Hour @ position " + i + " is null.");
+ }
+ hours[i] = hourLabels[i];
+ }
+ }
+
+ /**
+ * Default logic to generate the labels for the hours.
+ */
+ private void generateHourLabels() {
+ Date date = new Date();
+ date.setHours(0);
+ date.setMinutes(0);
+ String hour;
+
+ for (int i = 0; i < HOURS_IN_DAY; i++) {
+ date.setHours(i);
+ hour = timeFormat.format(date);
+ //shortTimeFormat.format(date);
+ hours[i] = hour;
+ }
+ }
+
+ /**
+ * Returns the currently configured day to start weeks in the
+ * MonthView. The default value is read from the
+ * CalendarConstants i18n configuration file, but it can be
+ * changed through the setFirstDayOfWeek method of this class.
+ *
+ * @return The currently configured day to start weeks, 0 for
+ * Sunday, 1 for Monday, and so on.
+ */
+ public int getFirstDayOfWeek() {
+ return firstDayOfWeek;
+ }
+
+ /**
+ * Configures the first day in the week when rendering the month view.
+ *
+ * @param firstDayOfWeek The first day of the week, where Sunday is
+ * represented by 0, Monday by
+ * 1, and so on.
+ */
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ this.firstDayOfWeek = Math.abs(firstDayOfWeek % 7);
+ }
+
+ public String getAm() {
+ return am;
+ }
+
+ public void setAm(String am) {
+ this.am = am;
+ }
+
+ public String getPm() {
+ return pm;
+ }
+
+ public void setPm(String pm) {
+ this.pm = pm;
+ }
+
+ /**
+ * Returns the currently configured label for the noon (12 p.m.).
+ *
+ * @return The configured label for the 12 p.m. time either through the
+ * CalendarConstants or the setNoon method
+ * of this class.
+ */
+ public String getNoon() {
+ return noon;
+ }
+
+ /**
+ * Configures the label to show for the 12 p.m.
+ *
+ * @param noon A label to show instead of 12 p.m.
+ */
+ public void setNoon(String noon) {
+ this.noon = noon;
+ }
+
+ /**
+ * Returns the configured labels for the 24 hours of the day. Some labels
+ * will vary, depending con configuration, for example "Noon"
+ * instead of "12".
+ *
+ * @return An array of Strings with the corresponding labels for the hours in
+ * a day
+ */
+ public String[] getHourLabels() {
+ return hours;
+ }
+
+ /**
+ * Indicates if we want to use the NoonLabel or the 12 p.m.
+ * @param use true if we want to use the NoonLabel, false otherwise
+ * @since 0.9.4
+ */
+ public void setUseNoonLabel(boolean use) {
+ this.useNoonLabel = use;
+ }
+
+ /**
+ * Indicates if we are using the NoonLabel for the 12 p.m.
+ * @since 0.9.4
+ */
+ public boolean isUseNoonLabel() {
+ return useNoonLabel;
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/CalendarSettings.java b/src/com/bradrydzewski/gwt/calendar/client/CalendarSettings.java
new file mode 100644
index 0000000..f7626d4
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/CalendarSettings.java
@@ -0,0 +1,182 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Brad Rydzewski
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see holidays = new ArrayList();
+ private int dayStartsAt = 0;
+
+ /*
+ * Clicks required to fire TimeBlockClickEvent.
+ */
+ private Click timeBlockClickNumber = Click.Single;
+
+ public CalendarSettings() {
+ }
+
+ public int getPixelsPerInterval() {
+ return pixelsPerInterval;
+ }
+
+ public void setPixelsPerInterval(final int px) {
+ pixelsPerInterval = px;
+ }
+
+ public int getIntervalsPerHour() {
+ return intervalsPerHour;
+ }
+
+ public void setIntervalsPerHour(final int intervals) {
+ intervalsPerHour = intervals;
+ }
+
+ public int getWorkingHourStart() {
+ return workingHourStart;
+ }
+
+ public void setWorkingHourStart(final int start) {
+ workingHourStart = start;
+ }
+
+ public int getWorkingHourEnd() {
+ return workingHourEnd;
+ }
+
+ public void setWorkingHourEnd(final int end) {
+ workingHourEnd = end;
+ }
+
+ public int getScrollToHour() {
+ return scrollToHour;
+ }
+
+ public void setScrollToHour(final int hour) {
+ scrollToHour = hour;
+ }
+
+ public boolean isEnableDragDrop() {
+ return enableDragDrop;
+ }
+
+ public void setEnableDragDrop(final boolean enableDragDrop) {
+ this.enableDragDrop = enableDragDrop;
+ }
+
+ public boolean isOffsetHourLabels() {
+ return offsetHourLabels;
+ }
+
+ public void setOffsetHourLabels(final boolean offsetHourLabels) {
+ this.offsetHourLabels = offsetHourLabels;
+ }
+
+ public Click getTimeBlockClickNumber() {
+ return timeBlockClickNumber;
+ }
+
+ public void setTimeBlockClickNumber(final Click timeBlockClickNumber) {
+ this.timeBlockClickNumber = timeBlockClickNumber;
+ }
+
+ /**
+ * @deprecated As of release 0.9.4 removed since is not really needed
+ */
+ @Deprecated
+ public void setEnableDragDropCreation(final boolean dragDropCreation) {
+
+ }
+
+ /**
+ * @deprecated As of release 0.9.4 removed since is not really needed
+ */
+ @Deprecated
+ public boolean getEnableDragDropCreation() {
+ return false;
+ }
+
+ public void setDayStartsAt(int dayStartsAt) {
+ this.dayStartsAt = dayStartsAt;
+ DateUtils.setDayStartsAt(dayStartsAt);
+ }
+
+ public int getDayStartsAt() {
+ return dayStartsAt;
+ }
+
+ /**
+ *
+ * @param showWeekNumbers
+ * @since 0.9.4
+ */
+ public void setShowWeekNumbers(final boolean showWeekNumbers) {
+ this.showWeekNumbers = showWeekNumbers;
+ }
+
+ /**
+ *
+ * @since 0.9.4
+ */
+ public boolean isShowingWeekNumbers() {
+ return showWeekNumbers;
+ }
+
+ public void setHolidays(List holidays) {
+ this.holidays = holidays;
+ }
+
+ public List getHolidays() {
+ return holidays;
+ }
+
+ /**
+ *
+ * @since 0.9.4
+ */
+ public void setShowMultiday(final boolean visible) {
+ this.showMultiDay = visible;
+ }
+
+ /**
+ *
+ * @since 0.9.4
+ */
+ public boolean isMultidayVisible() {
+ return showMultiDay;
+ }
+
+ public enum Click {
+ Double, Single, Drag
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/CalendarView.java b/src/com/bradrydzewski/gwt/calendar/client/CalendarView.java
new file mode 100644
index 0000000..c94e6c9
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/CalendarView.java
@@ -0,0 +1,285 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see Subclasses will provide the
+ * details of rendering the calendar to visualize by day (Day View), monthly
+ * (month view), agenda (list view) and the logic implementing the user-input
+ * event processing.
+ *
+ * @author Brad Rydzewski
+ */
+public abstract class CalendarView implements HasSettings, HasWeekSelectionHandlers, HasDaySelectionHandlers {
+
+ /**
+ * Calendar widget bound to the view.
+ *
+ * @see CalendarWidget
+ */
+ protected CalendarWidget calendarWidget = null;
+
+ /**
+ * Number of days the calendar should display at a given time, 3 by
+ * default.
+ */
+ private int displayedDays = 3;
+
+ /**
+ * Attaches this view to the provided {@link CalendarWidget}.
+ *
+ * @param calendarWidget The interactive widget containing the calendar
+ */
+ public void attach(CalendarWidget calendarWidget) {
+ this.calendarWidget = calendarWidget;
+ }
+
+ /**
+ * Detaches this view from the currently associated {@link CalendarWidget}.
+ * TODO: The CalendarWidget might still have a reference to this
+ * CalendarView, is that correct??
+ */
+ public void detatch() {
+ calendarWidget = null;
+ }
+
+ /**
+ * Returns the CSS style name of this calendar view.
+ *
+ * @return The CSS style that should be used when rendering this calendar
+ * view
+ */
+ public abstract String getStyleName();
+
+ public void doSizing() {
+ }
+
+ public abstract void doLayout();
+
+ /**
+ * Configures this view component's currently selected
+ * appointment. Notification to the calendar widget associated
+ * is optional and controlled with the fireEvent flag.
+ *
+ * @param appointment The appointment in the calendar in-memory model to be
+ * configured as the currently selected
+ * @param fireEvent Indicates whether a selection event should be
+ * triggered by the parent widget so that it informs its
+ * set of registered listeners about the change
+ */
+// public void setSelectedAppointment(Appointment appointment,
+// boolean fireEvent) {
+// calendarWidget.setSelectedAppointment(appointment);
+// if (fireEvent) {
+// calendarWidget.fireSelectionEvent(appointment);
+// }
+// }
+
+ /**
+ * Configures this view component's currently selected
+ * appointment and notifies widget about the change in the
+ * model state.
+ *
+ * @param appointment The appointment in the calendar in-memory model to be
+ * configured as the currently selected
+ */
+// public void setSelectedAppointment(Appointment appointment) {
+// setSelectedAppointment(appointment, true);
+// }
+
+ /**
+ * Returns the configured number of days the calendar should display at a
+ * given time.
+ *
+ * @return The number of days this calendar view should display at a given
+ * time
+ */
+ public int getDisplayedDays() {
+ return displayedDays;
+ }
+
+ /**
+ * Sets the configured number of days the calendar should display at a given
+ * time.
+ *
+ * @param displayedDays The number of days this calendar view should display
+ * at a given time
+ */
+ public void setDisplayedDays(int displayedDays) {
+ this.displayedDays = displayedDays;
+ }
+
+ /* on clicks */
+ public abstract void onDoubleClick(Element element, Event event);
+
+ public abstract void onSingleClick(Element element, Event event);
+
+ public abstract void onMouseOver(Element element, Event event);
+
+ /**
+ * Processes user {@link com.google.gwt.event.dom.client.KeyCodes#KEY_DELETE}
+ * and {@link com.google.gwt.event.dom.client.KeyCodes.KEY_BACKSPACE}
+ * keystrokes. The CalendarView implementation is empty so that
+ * subclasses are not forced to implement it if no specific logic is needed
+ * for {@link com.google.gwt.event.dom.client.KeyCodes#KEY_DELETE} or
+ * {@link com.google.gwt.event.dom.client.KeyCodes#KEY_BACKSPACE} keystrokes.
+ */
+ public void onDeleteKeyPressed() {
+ }
+
+ /**
+ * Processes user {@link com.google.gwt.event.dom.client.KeyCodes#KEY_UP}
+ * keystrokes. The CalendarView implementation is empty so that
+ * subclasses are not forced to implement it if no specific logic is needed
+ * for {@link com.google.gwt.event.dom.client.KeyCodes#KEY_UP} keystrokes.
+ */
+ public void onUpArrowKeyPressed() {
+ }
+
+ /**
+ * Processes user {@link com.google.gwt.event.dom.client.KeyCodes#KEY_DOWN}
+ * keystrokes. The CalendarView implementation is empty so that
+ * subclasses are not forced to implement it if no specific logic is needed
+ * for {@link com.google.gwt.event.dom.client.KeyCodes#KEY_DOWN}
+ * keystrokes.
+ */
+ public void onDownArrowKeyPressed() {
+ }
+
+ /**
+ * Processes user {@link com.google.gwt.event.dom.client.KeyCodes#KEY_LEFT}
+ * keystrokes. The CalendarView implementation is empty so that
+ * subclasses are not forced to implement it if no specific logic is needed
+ * for {@link com.google.gwt.event.dom.client.KeyCodes#KEY_LEFT}
+ * keystrokes.
+ */
+ public void onLeftArrowKeyPressed() {
+ }
+
+ /**
+ * Processes user {@link com.google.gwt.event.dom.client.KeyCodes#KEY_RIGHT}
+ * keystrokes. The CalendarView implementation is empty so that
+ * subclasses are not forced to implement it if no specific logic is needed
+ * for {@link com.google.gwt.event.dom.client.KeyCodes#KEY_RIGHT}
+ * keystrokes.
+ */
+ public void onRightArrowKeyPressed() {
+ }
+
+ public abstract void onAppointmentSelected(Appointment appt);
+
+ public final void selectAppointment(Appointment appt) {
+ calendarWidget.setSelectedAppointment(appt, true);
+ }
+
+ public final void selectNextAppointment() {
+ calendarWidget.selectNextAppointment();
+ }
+
+ public final void selectPreviousAppointment() {
+ calendarWidget.selectPreviousAppointment();
+ }
+
+ public final void updateAppointment(Appointment toAppt) {
+ calendarWidget.fireUpdateEvent(toAppt);
+ }
+
+ public final void deleteAppointment(Appointment appt) {
+ calendarWidget.fireDeleteEvent(appt);
+ }
+
+ public final void openAppointment(Appointment appt) {
+ calendarWidget.fireOpenEvent(appt);
+ }
+
+ public final void createAppointment(Appointment appt) {
+ createAppointment(appt.getStart(), appt.getEnd());
+ }
+
+ public final void createAppointment(Date start, Date end) {
+ calendarWidget.fireTimeBlockClickEvent(start);
+ }
+
+ public void scrollToHour(int hour) {
+
+ }
+
+ public CalendarSettings getSettings() {
+ return calendarWidget.getSettings();
+ }
+
+ public void setSettings(CalendarSettings settings) {
+ calendarWidget.setSettings(settings);
+ }
+
+ protected void addDayClickHandler(final Label dayLabel, final Date day) {
+ dayLabel.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ fireSelectedDay(day);
+ }
+ });
+ }
+
+ protected void addWeekClickHandler(final Label weekLabel, final Date day) {
+ weekLabel.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ fireSelectedWeek(day);
+ }
+ });
+ }
+
+ protected void fireSelectedDay(final Date day) {
+ DaySelectionEvent.fire(this, day);
+ }
+
+ protected void fireSelectedWeek(final Date day) {
+ WeekSelectionEvent.fire(this, day);
+ }
+
+ public HandlerRegistration addWeekSelectionHandler(
+ WeekSelectionHandler handler) {
+ return calendarWidget.addHandler(handler, WeekSelectionEvent.getType());
+ }
+
+ public HandlerRegistration addDaySelectionHandler(
+ DaySelectionHandler handler) {
+ return calendarWidget.addHandler(handler, DaySelectionEvent.getType());
+ }
+
+ public void fireEvent(GwtEvent> event) {
+ calendarWidget.fireEvent(event);
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/CalendarViews.java b/src/com/bradrydzewski/gwt/calendar/client/CalendarViews.java
new file mode 100644
index 0000000..72bf540
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/CalendarViews.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see CalendarWidget is an {@link com.bradrydzewski.gwt.calendar.client.InteractiveWidget}
+ * that maintains a calendar model (a set of {@link Appointment} objects)
+ * managed through an {@link AppointmentManager}.
+ *
+ * TODO: Need Calendar "View" - CHECK
+ * TODO: Need CalendarSettings
+ * TODO: Need LayoutStrategy - CHECK
+ * TODO: Need DragDropStrategy
+ * TODO: Need ResizeStrategy ??? or is this same as DragDrop
+ * TODO: Add AppointmentBuilder ??? downside is that if the Appointment object is updated, need to refersh widget
+ *
+ * @author Brad Rydzewski
+ * @see com.bradrydzewski.gwt.calendar.client.InteractiveWidget
+ */
+public class CalendarWidget extends InteractiveWidget implements
+ HasSelectionHandlers, HasDeleteHandlers,
+ HasOpenHandlers, HasTimeBlockClickHandlers,
+ HasUpdateHandlers, HasDateRequestHandlers,
+ HasMouseOverHandlers,
+ HasLayout, HasAppointments {
+
+ /**
+ * Set to true if the calendar layout is suspended and cannot
+ * be triggered.
+ */
+ private boolean layoutSuspended = false;
+
+ /**
+ * Set to true if the calendar is pending the layout of its
+ * appointments.
+ */
+ private boolean layoutPending = false;
+
+ /**
+ * The date currently displayed by the calendar. Set to current system date
+ * by default.
+ */
+ private Date date;
+
+ /**
+ * Calendar settings, set to default.
+ */
+ private CalendarSettings settings = CalendarSettings.DEFAULT_SETTINGS;
+
+ /**
+ * The component to manage the set of appointments displayed by this
+ * CalendarWidget.
+ */
+ private AppointmentManager appointmentManager = null;
+
+ private CalendarView view = null;
+
+ /**
+ * Creates a CalendarWidget with an empty set of appointments
+ * and the current system date as the date currently displayed by the
+ * calendar.
+ */
+ public CalendarWidget() {
+ this(new Date());
+ }
+
+ public CalendarWidget(Date date) {
+ super();
+ appointmentManager = new AppointmentManager();
+ this.date = date;
+ DateUtils.resetTime(this.date);
+ }
+
+ /**
+ * Changes the current view of this calendar widget to the specified
+ * view. By setting this widget's current view the whole widget
+ * panel is cleared.
+ *
+ * @param view The {@link CalendarView} implementation to render this
+ * widget's underlying calendar
+ */
+ public final void setView(CalendarView view) {
+ this.getRootPanel().clear();
+ this.view = view;
+ this.view.attach(this);
+ this.setStyleName(this.view.getStyleName());
+ this.refresh();
+ }
+
+ public final CalendarView getView() {
+ return view;
+ }
+
+ public Date getDate() {
+ return (Date) date.clone();
+ }
+
+ public void setDate(Date date, int days) {
+ Date dateCopy = (Date)date.clone();
+ DateUtils.resetTime(dateCopy);
+ this.date = dateCopy;
+ view.setDisplayedDays(days);
+ refresh();
+ }
+
+ public void setDate(Date date) {
+ setDate(date, getDays());
+ }
+
+ /**
+ * Moves this calendar widget current date as many days as
+ * specified by the numOfDays parameter.
+ *
+ * @param numOfDays The number of days to change the calendar date forward
+ * (positive number) or backwards.
+ */
+ @SuppressWarnings("deprecation")
+ public void addDaysToDate(int numOfDays) {
+ this.date.setDate(this.date.getDate() + numOfDays);
+ }
+
+ public int getDays() {
+ return view == null ? 3 : view.getDisplayedDays();
+ }
+
+ public void setDays(int days) {
+ view.setDisplayedDays(days);
+ refresh();
+ }
+
+ /**
+ * Returns the collection of appointments in the underlying in-memory model
+ * of this calendar widget. Warning: the returned
+ * collection of apointments can be modified by client code, possibly
+ * breaking the system model invariants.
+ *
+ * @return The set of appointments to be displayed by this calendar widget
+ * @see AppointmentManager#getAppointments()
+ */
+ public List getAppointments() {
+ return appointmentManager.getAppointments();
+ }
+
+ /**
+ * Removes an appointment from the calendar.
+ *
+ * @param appointment the item to be removed.
+ */
+ public void removeAppointment(Appointment appointment) {
+ removeAppointment(appointment, false);
+ }
+
+ /**
+ * Removes the currently selected appointment from the model, if such
+ * appointment is set.
+ */
+ public void removeCurrentlySelectedAppointment() {
+ appointmentManager.removeCurrentlySelectedAppointment();
+ }
+
+ /**
+ * Removes an appointment from the calendar.
+ *
+ * @param appointment the item to be removed.
+ * @param fireEvents true to allow deletion events to be
+ * fired
+ */
+ public void removeAppointment(Appointment appointment, boolean fireEvents) {
+ boolean commitChange = true;
+
+ if (fireEvents) {
+ commitChange = DeleteEvent.fire(this, getSelectedAppointment());
+ }
+
+ if (commitChange) {
+ appointmentManager.removeAppointment(appointment);
+ refresh();
+ }
+ }
+
+ /**
+ * Resets the "currently selected" appointment of this calendar.
+ *
+ * @see com.bradrydzewski.gwt.calendar.client.AppointmentManager
+ */
+ public void resetSelectedAppointment() {
+ appointmentManager.resetSelectedAppointment();
+ }
+
+ /**
+ * Adds an appointment to the calendar.
+ *
+ * @param appointment item to be added
+ */
+ public void addAppointment(Appointment appointment) {
+ if (appointment == null) {
+ throw new NullPointerException("Added appointment cannot be null.");
+ }
+ appointmentManager.addAppointment(appointment);
+ refresh();
+ }
+
+ /**
+ * Adds each appointment in the list to the calendar.
+ *
+ * @param appointments items to be added.
+ */
+ public void addAppointments(List appointments) {
+ appointmentManager.addAppointments(appointments);
+ refresh();
+ }
+
+ /**
+ * Clears all appointment items.
+ */
+ public void clearAppointments() {
+ appointmentManager.clearAppointments();
+ refresh();
+ }
+
+ /**
+ * Sets the currently selected item.
+ *
+ * @param appointment the item to be selected, or null to
+ * de-select all items.
+ */
+ public void setSelectedAppointment(Appointment appointment) {
+ setSelectedAppointment(appointment, true);
+ }
+
+ public void setSelectedAppointment(Appointment appointment,
+ boolean fireEvents) {
+ appointmentManager.setSelectedAppointment(appointment);
+ if (fireEvents) {
+ fireSelectionEvent(appointment);
+ }
+ }
+
+ /**
+ * Indicates whether there is a "currently selected" appointment
+ * at the moment.
+ *
+ * @return true if there is an appointment currently selected,
+ * false if it is null.
+ * @see com.bradrydzewski.gwt.calendar.client.AppointmentManager#hasAppointmentSelected()
+ */
+ public boolean hasAppointmentSelected() {
+ return appointmentManager.hasAppointmentSelected();
+ }
+
+ /**
+ * Gets the currently selected item.
+ *
+ * @return the selected item.
+ */
+ public Appointment getSelectedAppointment() {
+ return appointmentManager.getSelectedAppointment();
+ }
+
+ /**
+ * Tells whether the passed appointment is the currently
+ * selected appointment.
+ *
+ * @param appointment The appointment to test to be the currently selected
+ * @return true if there is a currently selected appointment
+ * and happens to be equal to the passed appointment
+ * @see com.bradrydzewski.gwt.calendar.client.AppointmentManager#isTheSelectedAppointment(Appointment)
+ */
+ public boolean isTheSelectedAppointment(Appointment appointment) {
+ return appointmentManager.isTheSelectedAppointment(appointment);
+ }
+
+ /**
+ * Performs all layout calculations for the list of appointments and resizes
+ * the Calendar View appropriately.
+ */
+ protected void refresh() {
+ if (layoutSuspended) {
+ layoutPending = true;
+ return;
+ }
+
+ appointmentManager.resetHoveredAppointment();
+ appointmentManager.sortAppointments();
+
+ doLayout();
+ doSizing();
+ }
+
+ public void doLayout() {
+ view.doLayout();
+ }
+
+ public void doSizing() {
+ view.doSizing();
+ }
+
+ public void onLoad() {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ refresh();
+ }
+ });
+ }
+
+ /**
+ * Suspends the calendar from performing a layout. This can be useful when
+ * adding a large number of appointments at a time, since a layout is
+ * performed each time an appointment is added.
+ */
+ public void suspendLayout() {
+ layoutSuspended = true;
+ }
+
+ /**
+ * Allows the calendar to perform a layout, sizing the component and placing
+ * all appointments. If a layout is pending it will get executed when this
+ * method is called.
+ */
+ public void resumeLayout() {
+ layoutSuspended = false;
+
+ if (layoutPending) {
+ refresh();
+ }
+ }
+
+ public CalendarSettings getSettings() {
+ return this.settings;
+ }
+
+ public void setSettings(CalendarSettings settings) {
+ this.settings = settings;
+ }
+
+ public void scrollToHour(int hour) {
+ view.scrollToHour(hour);
+ }
+
+ public boolean selectPreviousAppointment() {
+
+ boolean selected = appointmentManager.selectPreviousAppointment();
+ if (selected) {
+ fireSelectionEvent(getSelectedAppointment());
+ }
+
+ return selected;
+ }
+
+ public boolean selectNextAppointment() {
+ boolean selected = appointmentManager.selectNextAppointment();
+ if (selected) {
+ fireSelectionEvent(getSelectedAppointment());
+ }
+ return selected;
+ }
+
+ @Override
+ public void onDeleteKeyPressed() {
+ view.onDeleteKeyPressed();
+ }
+
+ @Override
+ public void onDoubleClick(Element element, Event event) {
+ view.onDoubleClick(element, event);
+ }
+
+ @Override
+ public void onDownArrowKeyPressed() {
+ view.onDownArrowKeyPressed();
+ }
+
+ @Override
+ public void onLeftArrowKeyPressed() {
+ view.onLeftArrowKeyPressed();
+ }
+
+ @Override
+ public void onMouseDown(Element element, Event event) {
+ view.onSingleClick(element, event);
+ }
+
+ public void onMouseOver(Element element, Event event) {
+ view.onMouseOver(element, event);
+ }
+
+ @Override
+ public void onRightArrowKeyPressed() {
+ view.onRightArrowKeyPressed();
+ }
+
+ @Override
+ public void onUpArrowKeyPressed() {
+ view.onUpArrowKeyPressed();
+ }
+
+ public void fireOpenEvent(Appointment appointment) {
+ OpenEvent.fire(this, appointment);
+ }
+
+ public void fireDeleteEvent(Appointment appointment) {
+
+ //fire the event to notify the client
+ boolean allow = DeleteEvent.fire(this, appointment);
+
+ if (allow) {
+ appointmentManager.removeAppointment(appointment);
+ refresh();
+ }
+ }
+
+ public void fireSelectionEvent(Appointment appointment) {
+ view.onAppointmentSelected(appointment);
+ SelectionEvent.fire(this, appointment);
+ }
+
+ public void fireMouseOverEvent(
+ Appointment appointment, Element element) {
+ //we need to make sure we aren't re-firing the event
+ // for the same appointment. This is a bit problematic,
+ // because the mouse over event will fire for an appointment's
+ // child elements (title label, footer, body, for example)
+ // and will cause this method to be called with a null
+ // appointment. this is a temp workaround, but basically
+ // an appointment cannot be hovered twice in a row
+ if (appointment != null
+ && !appointment.equals(appointmentManager
+ .getHoveredAppointment())) {
+ appointmentManager.setHoveredAppointment(appointment);
+ MouseOverEvent.fire(this, appointment, element);
+ }
+ }
+
+ public void fireTimeBlockClickEvent(Date date) {
+ TimeBlockClickEvent.fire(this, date);
+ }
+
+ public void fireCreateEvent(Appointment appointment) {
+ boolean allow = CreateEvent.fire(this, appointment);
+ if (!allow) {
+ appointmentManager.rollback();
+ refresh();
+ }
+ }
+
+ public void fireDateRequestEvent(Date date) {
+ DateRequestEvent.fire(this, date);
+ }
+
+ public void fireDateRequestEvent(Date date, Element clicked) {
+ DateRequestEvent.fire(this, date, clicked);
+ }
+
+ public void fireUpdateEvent(Appointment appointment) {
+ //refresh the appointment
+ refresh();
+ //fire the event to notify the client
+ boolean allow = UpdateEvent.fire(this, appointment);
+
+ if (!allow) {
+ appointmentManager.rollback();
+ refresh();
+ }
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler handler) {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ public HandlerRegistration addDeleteHandler(
+ DeleteHandler handler) {
+ return addHandler(handler, DeleteEvent.getType());
+ }
+
+ public HandlerRegistration addMouseOverHandler(
+ MouseOverHandler handler) {
+ return addHandler(handler, MouseOverEvent.getType());
+ }
+
+ public HandlerRegistration addTimeBlockClickHandler(
+ TimeBlockClickHandler handler) {
+ return addHandler(handler, TimeBlockClickEvent.getType());
+ }
+
+ public HandlerRegistration addUpdateHandler(UpdateHandler handler) {
+ return addHandler(handler, UpdateEvent.getType());
+ }
+
+ public HandlerRegistration addCreateHandler(
+ CreateHandler handler) {
+ return addHandler(handler, CreateEvent.getType());
+ }
+
+ public HandlerRegistration addOpenHandler(
+ OpenHandler handler) {
+ return addHandler(handler, OpenEvent.getType());
+ }
+
+ public HandlerRegistration addDateRequestHandler(
+ DateRequestHandler handler) {
+ return addHandler(handler, DateRequestEvent.getType());
+ }
+
+ public void addToRootPanel(Widget widget) {
+ getRootPanel().add(widget);
+ }
+
+ public void setRollbackAppointment(Appointment appt) {
+ appointmentManager.setRollbackAppointment(appt);
+ }
+
+ public void setCommittedAppointment(Appointment appt) {
+ appointmentManager.setCommittedAppointment(appt);
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/DateUtils.java b/src/com/bradrydzewski/gwt/calendar/client/DateUtils.java
new file mode 100644
index 0000000..795a78f
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/DateUtils.java
@@ -0,0 +1,405 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009-2011 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see null-safe way to return the number of milliseconds
+ * on a date.
+ *
+ * @param date The date whose value in milliseconds will be returned
+ * @return The number of milliseconds in date, 0
+ * (zero) if date is null.
+ */
+ private static long safeInMillis(Date date) {
+ return date != null ? date.getTime() : 0;
+ }
+
+ /**
+ * Returns the number of days between the passed dates.
+ *
+ * @param endDate The upper limit of the date range
+ * @param startDate The lower limit of the date range
+ * @return The number of days between endDate and
+ * starDate (inclusive)
+ */
+ @SuppressWarnings(value = "deprecation")
+ public static int differenceInDays(Date endDate, Date startDate) {
+ int difference = 0;
+ if (!areOnTheSameDay(endDate, startDate)) {
+ int endDateOffset = -(endDate.getTimezoneOffset() * 60 * 1000);
+ long endDateInstant = endDate.getTime() + endDateOffset;
+ int startDateOffset = -(startDate.getTimezoneOffset() * 60 * 1000);
+ long startDateInstant = startDate.getTime() + startDateOffset;
+ double differenceDouble =
+ (double) Math.abs(endDateInstant - startDateInstant) /
+ (double) MILLIS_IN_A_DAY;
+ differenceDouble = Math.max(1.0D, differenceDouble);
+ difference = (int) differenceDouble;
+ }
+ return difference;
+ }
+
+ /**
+ * Returns the full year (4-digits) of the passed date.
+ * @param date The date whose year will be returned
+ * @return The full year of the passed date.
+ */
+ @SuppressWarnings("deprecation")
+ public static int year(Date date) {
+ return 1900 + date.getYear();
+ }
+
+ /**
+ * Moves a date shift days. A clone of date to
+ * prevent undesired object modifications.
+ *
+ * @param date The date to shift
+ * @param shift The number of days to push the original date
+ * forward
+ * @return A new date pushed shift days forward
+ */
+ @SuppressWarnings("deprecation")
+ public static Date shiftDate(Date date, int shift) {
+ Date result = (Date) date.clone();
+ result.setDate(date.getDate() + shift);
+ return result;
+ }
+
+ /**
+ * Resets the date to have no time modifiers (hours, minutes, seconds.)
+ *
+ * @param date The date to reset
+ */
+ @SuppressWarnings("deprecation")
+ public static void resetTime(Date date) {
+ long milliseconds = safeInMillis(date);
+ milliseconds = (milliseconds / 1000) * 1000;
+ date.setTime(milliseconds);
+ date.setHours(DateUtils.getDayStartsAt());
+ date.setMinutes(0);
+ date.setSeconds(0);
+ }
+
+ /**
+ * Indicates whether two dates are on the same date by comparing their day,
+ * month and year values. Time values such as hours and minutes are not
+ * considered in this comparison.
+ *
+ * @param dateOne The first date to test
+ * @param dateTwo The second date to test
+ * @return true if both dates have their date,
+ * month and year properties with the
+ * exact same values (whatever they are)
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean areOnTheSameDay(Date dateOne, Date dateTwo) {
+ Date end;
+ Date start;
+
+ if (dateTwo.getTime() > dateOne.getTime()) {
+ end = (Date) dateTwo.clone();
+ start = (Date) dateOne.clone();
+ } else {
+ start = (Date) dateTwo.clone();
+ end = (Date) dateOne.clone();
+ }
+
+ if (DateUtils.dayStartsAt != 0) {
+ long time = start.getTime()
+ - (1000 * 60 * 60 * DateUtils.dayStartsAt);
+ start = new Date(time);
+
+ time = end.getTime()
+ - (1000 * 60 * 60 * DateUtils.dayStartsAt);
+ end = new Date(time);
+ }
+
+ return start.getDate() == end.getDate()
+ && start.getMonth() == end.getMonth()
+ && start.getYear() == end.getYear();
+ }
+
+ /**
+ * Indicates whether two dates are on the same month of the same year.
+ *
+ * @param dateOne The first date of the comparison
+ * @param dateTwo The second date of the comparison
+ * @return true if both dates have the same year and month,
+ * false otherwise
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean areOnTheSameMonth(Date dateOne, Date dateTwo) {
+ Date end = (Date)dateTwo.clone();
+
+ if (DateUtils.dayStartsAt != 0) {
+ long time = end.getTime() - (1000 * 60 * 60 * DateUtils.dayStartsAt);
+ end = new Date(time);
+ }
+
+ return dateOne.getYear() == end.getYear() &&
+ dateOne.getMonth() == end.getMonth();
+ }
+
+ /**
+ * Returns a clone of the anyDayInMonth date set to the
+ * first day of whatever its month is.
+ *
+ * @param anyDayInMonth Any date on a month+year
+ * @return A clone of the anyDayInMonth date, representing the
+ * first day of that same month and year
+ */
+ @SuppressWarnings("deprecation")
+ public static Date firstOfTheMonth(Date anyDayInMonth) {
+ Date first = (Date) anyDayInMonth.clone();
+ first.setDate(1);
+ return first;
+ }
+
+ /**
+ * Moves the date of the passed object to be one day after whatever date it
+ * has.
+ *
+ * @param date An object representing a date
+ * @return The day
+ */
+ @SuppressWarnings("deprecation")
+ public static Date moveOneDayForward(Date date) {
+ date.setDate(date.getDate() + 1);
+ return date;
+ }
+
+ /**
+ * Returns the date corresponding to the first day of the next month relative
+ * to the passed date.
+ *
+ * @param date The reference date
+ * @return The first day of the next month, if the month of the passed date
+ * corresponds to december (11) one will be
+ * added to the year of the returned date.
+ */
+ @SuppressWarnings("deprecation")
+ public static Date firstOfNextMonth(Date date) {
+ Date firstOfNextMonth = null;
+ if (date != null) {
+ int year = date.getMonth() == 11 ? date.getYear() + 1 : date.getYear();
+ firstOfNextMonth = new Date(year, date.getMonth() + 1 % 11, 1);
+ }
+ return firstOfNextMonth;
+ }
+
+ /**
+ * Returns a day exactly 24 hours before the instant passed as
+ * date. // TODO: This logic should address the time zone
+ * offset
+ *
+ * @param date A point in time from which the moment 24 hours before will be
+ * calculated
+ * @return A new object 24 hours prior to the passed
+ * date
+ */
+ public static Date previousDay(Date date) {
+ return new Date(date.getTime() - MILLIS_IN_A_DAY);
+ }
+
+
+ /**
+ * Copies the hours, minutes and seconds in the source date into
+ * the target date object.
+ *
+ * @param source The date with the hour, minutes and seconds to be copied
+ * @param target The date whose time fields will be set
+ */
+ @SuppressWarnings("deprecation")
+ public static void copyTime(Date source, Date target) {
+ target.setHours(source.getHours());
+ target.setMinutes(source.getMinutes());
+ target.setSeconds(source.getSeconds());
+ }
+
+ /**
+ * Returns the amount of minutes elapsed since the beginning of the passed
+ * day.
+ *
+ * @param day The day to calculate the elapsed minutes
+ * @return The number of minutes since day started
+ */
+ @SuppressWarnings("deprecation")
+ public static int minutesSinceDayStarted(Date day) {
+ int minutes = (day.getHours() - dayStartsAt) * 60 + day.getMinutes();
+ if (day.getHours() < dayStartsAt) {
+ minutes = ((24 - dayStartsAt) + day.getHours()) * 60 + day.getMinutes();
+ }
+ return minutes;
+ }
+
+ /**
+ * Creates a new date with whatever date/time the passed date
+ * object represents.
+ *
+ * @param date The source date
+ * @return A new date object representing the same date and time as the passed
+ * object
+ */
+ public static Date newDate(Date date) {
+ Date result = null;
+ if (date != null) {
+ result = new Date(date.getTime());
+ }
+ return result;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static boolean isWeekend(final Date day) {
+ return day.getDay()==0 || day.getDay()==6;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Integer weekday(final Date date) {
+ int firstDayOfWeek = Integer.valueOf(CalendarFormat.INSTANCE.getFirstDayOfWeek());
+
+ int weekday = date.getDay();
+ if ((firstDayOfWeek == 1) && (weekday == 0)) {
+ weekday = 7;
+ }
+ return weekday;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static int calendarWeekIso(final Date inputDate) {
+ final int daysWeek = 7;
+ int firstDayOfWeek = Integer.valueOf(CalendarFormat.INSTANCE.getFirstDayOfWeek());
+
+ int thursdayDay = 4 + firstDayOfWeek;
+
+ Date thisThursday = new Date(inputDate.getYear(), inputDate.getMonth(),
+ inputDate.getDate() - weekday(inputDate) + thursdayDay);
+
+ Date firstThursdayOfYear = new Date(thisThursday.getYear(), 0, 1);
+
+ while (weekday(firstThursdayOfYear) != thursdayDay) {
+ firstThursdayOfYear.setDate(firstThursdayOfYear.getDate() + 1);
+ }
+
+ Date firstMondayOfYear = new Date(firstThursdayOfYear.getYear(), 0,
+ firstThursdayOfYear.getDate() - 3);
+
+ Long cw = (thisThursday.getTime() - firstMondayOfYear.getTime())
+ / MILLIS_IN_A_DAY / daysWeek + 1;
+
+ return cw.intValue();
+ }
+
+ /**
+ * Adds or subtracts the specified amount of days for the given Date.
+ *
+ * @param date
+ * A point in time
+ * @param days
+ * Number of days to add or substract
+ * @return A new object days after to the passed date
+ */
+ public static Date addDays(final Date date, final int days) {
+ return new Date(date.getTime() + (days * MILLIS_IN_A_DAY));
+ }
+
+ /**
+ * Adds or subtracts the specified amount of weeks for the given Date.
+ *
+ * @param date
+ * A point in time
+ * @param weeks
+ * Number of weeks to add or substract
+ * @return A new object weeks after to the passed date
+ */
+ public static Date addWeeks(final Date date, final int weeks) {
+ return new Date(date.getTime() + (weeks * (MILLIS_IN_A_DAY * DAYS_IN_A_WEEK)));
+ }
+
+ /**
+ * Adds or subtracts the specified amount of months for the given Date.
+ *
+ * @param date
+ * A point in time
+ * @param months
+ * Number of months to add or substract
+ * @return A new object weeks after to the passed date
+ */
+ public static Date addMonths(final Date date, final int months) {
+ return new Date(date.getTime() + (months * (MILLIS_IN_A_DAY * DAYS_IN_A_WEEK)));
+ }
+
+ /**
+ * Returns the total number of days between start and
+ * end. The calculation is rounded to take in account
+ * the saving day calculation.
+ *
+ * @param start
+ * The first day in the period
+ * @param end
+ * The last day in the period
+ * @return The number of days between start and
+ * end, the minimum difference being one (
+ * 1)
+ */
+ public static int daysInPeriod(final Date start, final Date end) {
+ long diff = end.getTime() - start.getTime();
+ double days = ((double)diff / MILLIS_IN_A_DAY) + 1;
+
+ Long result = Math.round(days);
+ return result.intValue();
+ }
+
+ /**
+ * Calculates the amount of days in the month the
+ * @param day
+ * @return
+ */
+ public static int daysInMonth(final Date date) {
+ return 32 - new Date(date.getYear(), date.getMonth(), 32).getDate();
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/HasAppointments.java b/src/com/bradrydzewski/gwt/calendar/client/HasAppointments.java
new file mode 100644
index 0000000..fc5dc19
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/HasAppointments.java
@@ -0,0 +1,17 @@
+package com.bradrydzewski.gwt.calendar.client;
+
+import java.util.List;
+
+public interface HasAppointments {
+
+ void removeAppointment(Appointment appointment);
+ void removeAppointment(Appointment appointment, boolean fireEvents);
+ void addAppointment(Appointment appointment);
+ void addAppointments(List appointments);
+ void clearAppointments();
+ Appointment getSelectedAppointment();
+ void setSelectedAppointment(Appointment appointment);
+ void setSelectedAppointment(Appointment appointment,
+ boolean fireEvents);
+ boolean hasAppointmentSelected();
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/HasLayout.java b/src/com/bradrydzewski/gwt/calendar/client/HasLayout.java
new file mode 100644
index 0000000..d539441
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/HasLayout.java
@@ -0,0 +1,21 @@
+package com.bradrydzewski.gwt.calendar.client;
+
+public interface HasLayout {
+
+ /**
+ * Forces the widget to re-calculate and perform
+ * layout operations.
+ */
+ public void doLayout();
+
+ /**
+ * Suspends the widget from performing layout operations.
+ */
+ public void suspendLayout();
+
+ /**
+ * Enables the widget to perform layout operations. Any pending layout
+ * operations will be executed.
+ */
+ public void resumeLayout();
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/HasSettings.java b/src/com/bradrydzewski/gwt/calendar/client/HasSettings.java
new file mode 100644
index 0000000..1d890fe
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/HasSettings.java
@@ -0,0 +1,7 @@
+package com.bradrydzewski.gwt.calendar.client;
+
+public interface HasSettings {
+
+ public CalendarSettings getSettings();
+ public void setSettings(CalendarSettings settings);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/InteractiveWidget.java b/src/com/bradrydzewski/gwt/calendar/client/InteractiveWidget.java
new file mode 100644
index 0000000..216244c
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/InteractiveWidget.java
@@ -0,0 +1,253 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2009 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see onXXXKeyPressed,
+ * onMouseDown and onDoubleClick methods to provide
+ * the custom event processing logic.
+ *
+ * @author Brad Rydzewski
+ */
+public abstract class InteractiveWidget extends Composite {
+
+ /**
+ * Focus widget used to add keyboard and mouse focus to a calendar.
+ */
+ private FocusPanel focusPanel = new FocusPanel();
+
+ /**
+ * Main panel hold all other components.
+ */
+ protected FlowPanel rootPanel = new FlowPanel();
+
+ /**
+ * Used by focus widget to make sure a key stroke is only handled once by
+ * the calendar.
+ */
+ private boolean lastWasKeyDown = false;
+
+ public InteractiveWidget() {
+
+ initWidget(rootPanel);
+
+ //Sink events, mouse and keyboard for now
+ sinkEvents(Event.ONMOUSEDOWN | Event.ONDBLCLICK | Event.KEYEVENTS | Event.ONMOUSEOVER);
+
+ hideFocusPanel();
+
+ //Add key handler events to the focus panel
+ focusPanel.addKeyPressHandler(new KeyPressHandler() {
+ public void onKeyPress(KeyPressEvent event) {
+ if (!lastWasKeyDown) {
+ keyboardNavigation(event.getNativeEvent().getKeyCode());
+ }
+ lastWasKeyDown = false;
+ }
+ });
+
+ focusPanel.addKeyUpHandler(new KeyUpHandler() {
+ public void onKeyUp(KeyUpEvent event) {
+ lastWasKeyDown = false;
+ }
+ });
+ focusPanel.addKeyDownHandler(new KeyDownHandler() {
+ public void onKeyDown(KeyDownEvent event) {
+ keyboardNavigation(event.getNativeEvent().getKeyCode());
+ lastWasKeyDown = true;
+ }
+ });
+ }
+
+ /**
+ * Makes the widget's focus panel invisible.
+ */
+ private void hideFocusPanel() {
+/*
+ focusPanel.setVisible(false);
+ RootPanel.get().add(focusPanel);
+ DOM.setStyleAttribute(focusPanel.getElement(), "position", "absolute");
+ DOM.setStyleAttribute(focusPanel.getElement(), "top", "0");
+ DOM.setStyleAttribute(focusPanel.getElement(), "left", "0");
+ DOM.setStyleAttribute(focusPanel.getElement(), "height", "100%");
+ DOM.setStyleAttribute(focusPanel.getElement(), "width", "100%");
+*/
+ RootPanel.get().add(focusPanel);
+ DOM.setStyleAttribute(focusPanel.getElement(), "position", "absolute");
+ DOM.setStyleAttribute(focusPanel.getElement(), "top", "-10");
+ DOM.setStyleAttribute(focusPanel.getElement(), "left", "-10");
+ DOM.setStyleAttribute(focusPanel.getElement(), "height", "0px");
+ DOM.setStyleAttribute(focusPanel.getElement(), "width", "0px");
+ }
+
+ public ComplexPanel getRootPanel() {
+ return rootPanel;
+ }
+
+ /**
+ * Processes mouse double-click events. Concrete interactive widgets should
+ * provide the component's specific logic.
+ *
+ * @param element The HTML DOM element that originated the event
+ */
+ public abstract void onDoubleClick(Element element, Event event);
+
+ /**
+ * Processes mouse over events. Concrete interactive widgets
+ * should provide the component's specific logic.
+ *
+ * @param element The HTML DOM element that originated the event
+ * @param event The HTML DOM event that was triggered
+ */
+ public abstract void onMouseOver(Element element, Event event);
+
+ /**
+ * Processes mouse button pressing events. Concrete interactive widgets
+ * should provide the component's specific logic.
+ *
+ * @param element The HTML DOM element that originated the event
+ */
+ public abstract void onMouseDown(Element element, Event event);
+
+ /**
+ * Processes {@link com.google.gwt.event.dom.client.KeyCodes.KEY_DELETE}
+ * and {@link com.google.gwt.event.dom.client.KeyCodes.KEY_BACKSPACE}
+ * keystrokes. Concrete interactive widgets should provide the component's
+ * specific logic.
+ */
+ public abstract void onDeleteKeyPressed();
+
+ /**
+ * Processes {@link com.google.gwt.event.dom.client.KeyCodes.KEY_UP}
+ * keystrokes. Concrete interactive widgets should provide the component's
+ * specific logic.
+ */
+ public abstract void onUpArrowKeyPressed();
+
+ /**
+ * Processes {@link com.google.gwt.event.dom.client.KeyCodes.KEY_DOWN}
+ * keystrokes. Concrete interactive widgets should provide the component's
+ * specific logic.
+ */
+ public abstract void onDownArrowKeyPressed();
+
+ /**
+ * Processes {@link com.google.gwt.event.dom.client.KeyCodes.KEY_LEFT}
+ * keystrokes. Concrete interactive widgets should provide the component's
+ * specific logic.
+ */
+ public abstract void onLeftArrowKeyPressed();
+
+ /**
+ * Processes {@link com.google.gwt.event.dom.client.KeyCodes.KEY_RIGHT}
+ * keystrokes. Concrete interactive widgets should provide the component's
+ * specific logic.
+ */
+ public abstract void onRightArrowKeyPressed();
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ int eventType = DOM.eventGetType(event);
+ Element element = DOM.eventGetTarget(event);
+
+ switch (eventType) {
+ case Event.ONDBLCLICK: {
+ onDoubleClick(element, event);
+ focusPanel.setFocus(true);
+ break;
+ }
+ case Event.ONMOUSEDOWN: {
+ if (DOM.eventGetCurrentTarget(event) == getElement()) {
+
+ onMouseDown(element, event);
+ focusPanel.setFocus(true);
+ //Cancel events so Firefox / Chrome don't
+ //give child widgets with scrollbars focus.
+ //TODO: Should not cancel onMouseDown events in the event an appointment would have a child widget with a scrollbar (if this would ever even happen).
+ DOM.eventCancelBubble(event, true);
+ DOM.eventPreventDefault(event);
+ return;
+ }
+ }
+ case Event.ONMOUSEOVER:{
+ if (DOM.eventGetCurrentTarget(event) == getElement()){
+ onMouseOver(element, event);
+ DOM.eventCancelBubble(event, true);
+ DOM.eventPreventDefault(event);
+ return;
+ }
+ }
+ }
+
+ super.onBrowserEvent(event);
+ }
+
+ /**
+ * Dispatches the processing of a key being pressed to the this widget
+ * onXXXXKeyPressed methods.
+ *
+ * @param key Pressed key code
+ */
+ protected void keyboardNavigation(int key) {
+ switch (key) {
+ case KeyCodes.KEY_BACKSPACE: {
+ onDeleteKeyPressed();
+ break;
+ }
+ case KeyCodes.KEY_DELETE: {
+ onDeleteKeyPressed();
+ break;
+ }
+ case KeyCodes.KEY_LEFT: {
+ onLeftArrowKeyPressed();
+ break;
+ }
+ case KeyCodes.KEY_UP: {
+ onUpArrowKeyPressed();
+ break;
+ }
+ case KeyCodes.KEY_RIGHT: {
+ onRightArrowKeyPressed();
+ break;
+ }
+ case KeyCodes.KEY_DOWN: {
+ onDownArrowKeyPressed();
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/ThemeAppointmentStyle.java b/src/com/bradrydzewski/gwt/calendar/client/ThemeAppointmentStyle.java
new file mode 100644
index 0000000..21a6827
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/ThemeAppointmentStyle.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2011 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see appointmentAdapterList =
+ new ArrayList();
+
+ /**
+ * DateTime format used to represent a day.
+ */
+ private static final DateTimeFormat DEFAULT_DATE_FORMAT =
+ DateTimeFormat.getFormat("EEE MMM d");
+
+ /**
+ * DateTime format used when displaying an appointments start and end time.
+ */
+ private static final DateTimeFormat DEFAULT_TIME_FORMAT =
+ DateTimeFormat.getShortTimeFormat();
+
+ /**
+ * Style used to format this view.
+ */
+ private String styleName = "gwt-cal-ListView";
+
+ /**
+ * Adds the calendar view to the calendar widget and performs required formatting.
+ */
+ public void attach(CalendarWidget widget) {
+ super.attach(widget);
+
+ appointmentGrid.setCellPadding(5);
+ appointmentGrid.setCellSpacing(0);
+ appointmentGrid.setBorderWidth(0);
+ appointmentGrid.setWidth("100%");
+ //DOM.setStyleAttribute(appointmentGrid.getElement(), "tableLayout", "fixed");
+ calendarWidget.getRootPanel().add(appointmentGrid);
+ calendarWidget.getRootPanel().add(appointmentGrid);
+ }
+
+ /**
+ * Gets the style name associated with this particular view
+ * @return Style name.
+ */
+ public String getStyleName() {
+ return styleName;
+ }
+
+ @Override
+ public void doLayout() {
+
+ appointmentAdapterList.clear();
+ appointmentGrid.clear();
+ for (int i = appointmentGrid.getRowCount() - 1; i >= 0; i--) {
+ appointmentGrid.removeRow(i);
+ }
+
+
+ //Get the start date, make sure time is 0:00:00 AM
+ Date startDate = (Date) calendarWidget.getDate().clone();
+ Date today = new Date();
+ Date endDate = (Date) calendarWidget.getDate().clone();
+ endDate.setDate(endDate.getDate() + 1);
+ DateUtils.resetTime(today);
+ DateUtils.resetTime(startDate);
+ DateUtils.resetTime(endDate);
+
+ int row = 0;
+
+ for (int i = 0; i < calendarWidget.getDays(); i++) {
+
+ // Filter the list by date
+ List filteredList = AppointmentUtil
+ .filterListByDate(calendarWidget.getAppointments(), startDate, endDate);
+
+ if (filteredList != null && filteredList.size() > 0) {
+
+ appointmentGrid.setText(row, 0, DEFAULT_DATE_FORMAT.format(startDate));
+
+ appointmentGrid.getCellFormatter().setVerticalAlignment(row, 0,
+ HasVerticalAlignment.ALIGN_TOP);
+ appointmentGrid.getFlexCellFormatter().setRowSpan(row, 0,
+ filteredList.size());
+ appointmentGrid.getFlexCellFormatter().setStyleName(row, 0,
+ "dateCell");
+ int startingCell = 1;
+
+ //Row styles will alternate, so we set the style accordingly
+ String rowStyle = (i % 2 == 0) ? "row" : "row-alt";
+
+ //If a Row represents the current date (Today) then we style it differently
+ if (startDate.equals(today))
+ rowStyle += "-today";
+
+
+ for (Appointment appt : filteredList) {
+
+ // add the time range
+ String timeSpanString = DEFAULT_TIME_FORMAT.format(appt.getStart())
+ + " - " + DEFAULT_TIME_FORMAT.format(appt.getEnd());
+ Label timeSpanLabel = new Label(timeSpanString.toLowerCase());
+ appointmentGrid.setWidget(row, startingCell, timeSpanLabel);
+
+
+
+ // add the title and description
+ FlowPanel titleContainer = new FlowPanel();
+ InlineLabel titleLabel = new InlineLabel(appt.getTitle());
+ titleContainer.add(titleLabel);
+ InlineLabel descLabel = new InlineLabel(" - "
+ + appt.getDescription());
+ descLabel.setStyleName("descriptionLabel");
+ titleContainer.add(descLabel);
+ appointmentGrid.setWidget(row, startingCell + 1,
+ titleContainer);
+
+
+
+ SimplePanel detailContainerPanel = new SimplePanel();
+ AppointmentDetailPanel detailContainer= new AppointmentDetailPanel(detailContainerPanel, appt);
+
+ appointmentAdapterList.add(new AgendaViewAppointmentAdapter(
+ titleLabel, timeSpanLabel, detailContainerPanel,
+ detailContainer.getMoreDetailsLabel(), appt));
+
+ //add the detail container
+ titleContainer.add(detailContainer);
+
+ //add click handlers to title, date and details link
+ timeSpanLabel.addClickHandler(appointmentClickHandler);
+ titleLabel.addClickHandler(appointmentClickHandler);
+ detailContainer.getMoreDetailsLabel().addClickHandler(
+ appointmentClickHandler);
+
+
+
+
+ // Format the Cells
+ appointmentGrid.getCellFormatter().setVerticalAlignment(
+ row, startingCell, HasVerticalAlignment.ALIGN_TOP);
+ appointmentGrid.getCellFormatter().setVerticalAlignment(
+ row, startingCell + 1,
+ HasVerticalAlignment.ALIGN_TOP);
+ appointmentGrid.getCellFormatter().setStyleName(row,
+ startingCell, "timeCell");
+ appointmentGrid.getCellFormatter().setStyleName(row,
+ startingCell + 1, "titleCell");
+ appointmentGrid.getRowFormatter().setStyleName(row,
+ rowStyle);
+
+ // increment the row
+ // make sure the starting column is reset to 0
+ startingCell = 0;
+ row++;
+ }
+ }
+
+ // increment the date
+ startDate.setDate(startDate.getDate() + 1);
+ endDate.setDate(endDate.getDate() + 1);
+ }
+ }
+
+
+ /**
+ * Handles appointments being clicked. Based on the clicked widget will determine
+ * exactly which appointment was clicked and may 1) expand / collaps the appointment
+ * details 2) set the selected appointment or 3) trigger an appointment clicked
+ * event.
+ */
+ private ClickHandler appointmentClickHandler = new ClickHandler() {
+
+ public void onClick(ClickEvent event) {
+
+ //get the appointment adapter based on the clicked widget
+ AgendaViewAppointmentAdapter adapter =
+ getAppointmentFromClickedWidget((Widget)event.getSource());
+
+ if (adapter != null) {
+ if(event.getSource().equals(adapter.getDetailsLabel())) {
+ //set the selected appointment
+ calendarWidget.setSelectedAppointment(adapter.getAppointment(), true);
+ //setSelectedAppointment(adapter.getAppointment(),false);
+ //calendarWidget.fireOpenEvent(adapter.getAppointment());
+ } else {
+ //expand the panel if it is not yet expended
+ adapter.getDetailsPanel().setVisible(
+ !adapter.getDetailsPanel().isVisible());
+ }
+ }
+ }
+ };
+
+ /**
+ * Given Widget w determine which appointment was clicked. This is necessary because
+ * each appointment has 3 widgets that can be clicked - the title, date range and
+ * description.
+ * @param w Widget that was clicked.
+ * @return Appointment mapped to that widget.
+ */
+ protected AgendaViewAppointmentAdapter getAppointmentFromClickedWidget(Widget w) {
+ for(AgendaViewAppointmentAdapter a : appointmentAdapterList) {
+ if(w.equals(a.dateLabel) || w.equals(a.detailsLabel) ||
+ w.equals(a.titleLabel) || w.equals(a.getDetailsPanel())) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onDoubleClick(Element element, Event event) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onSingleClick(Element element, Event event) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onMouseOver(Element element, Event event) {
+
+ }
+
+ @Override
+ public void onAppointmentSelected(Appointment appt) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentAdapter.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentAdapter.java
new file mode 100644
index 0000000..be32bfd
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentAdapter.java
@@ -0,0 +1,170 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see intersectingBlocks;
+ private float top;
+ private float left;
+ private float width;
+ private float height;
+
+ public float getTop() {
+ return top;
+ }
+
+ public void setTop(float top) {
+ this.top = top;
+ }
+
+ public float getLeft() {
+ return left;
+ }
+
+ public void setLeft(float left) {
+ this.left = left;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void setWidth(float width) {
+ this.width = width;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public void setHeight(float height) {
+ this.height = height;
+ }
+
+ public AppointmentAdapter(Appointment appointment) {
+ this.appointment = appointment;
+ this.appointmentStart = minutesSinceDayStarted(appointment.getStart());
+ this.appointmentEnd = minutesSinceDayStarted(appointment.getEnd());
+ this.intersectingBlocks = new ArrayList();
+ }
+
+ public int getCellStart() {
+ return cellStart;
+ }
+
+ public void setCellStart(int cellStart) {
+ this.cellStart = cellStart;
+ }
+
+ public int getCellSpan() {
+ return cellSpan;
+ }
+
+ public void setCellSpan(int cellSpan) {
+ this.cellSpan = cellSpan;
+ }
+
+ public int getColumnStart() {
+ return columnStart;
+ }
+
+ public void setColumnStart(int columnStart) {
+ this.columnStart = columnStart;
+ }
+
+ public int getColumnSpan() {
+ return columnSpan;
+ }
+
+ public void setColumnSpan(int columnSpan) {
+ this.columnSpan = columnSpan;
+ }
+
+ public int getAppointmentStart() {
+ return appointmentStart;
+ }
+
+ public void setAppointmentStart(int appointmentStart) {
+ this.appointmentStart = appointmentStart;
+ }
+
+ public int getAppointmentEnd() {
+ return appointmentEnd;
+ }
+
+ public void setAppointmentEnd(int appointmentEnd) {
+ this.appointmentEnd = appointmentEnd;
+ }
+
+ public List getIntersectingBlocks() {
+ return intersectingBlocks;
+ }
+
+ public void setIntersectingBlocks(List intersectingBlocks) {
+ this.intersectingBlocks = intersectingBlocks;
+ }
+
+ public Appointment getAppointment() {
+ return appointment;
+ }
+
+ public float getCellPercentFill() {
+ return cellPercentFill;
+ }
+
+ public void setCellPercentFill(float cellPercentFill) {
+ this.cellPercentFill = cellPercentFill;
+ }
+
+ public float getCellPercentStart() {
+ return cellPercentStart;
+ }
+
+ public void setCellPercentStart(float cellPercentStart) {
+ this.cellPercentStart = cellPercentStart;
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentWidget.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentWidget.java
new file mode 100644
index 0000000..12d6b08
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentWidget.java
@@ -0,0 +1,241 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see appointmentWidgets = new ArrayList();
+ /**
+ * List of AppointmentAdapter objects that represent the currently selected
+ * appointment.
+ */
+ private List selectedAppointmentWidgets = new ArrayList();
+
+ private final DayViewStyleManager styleManager = GWT.create(DayViewStyleManager.class);
+
+ private DayViewResizeController resizeController = null;
+
+ private DayViewDropController dropController = null;
+
+ private PickupDragController dragController = null;
+
+ private DayViewResizeController proxyResizeController = null;
+
+ public DayView() {
+ super();
+ }
+
+ private int getMaxProxyHeight() {
+ // For a comfortable use, the Proxy should be, top 2/3 (66%) of the view
+ return (2 * (dayViewBody.getScrollPanel().getOffsetHeight() / 3));
+ }
+
+ @SuppressWarnings("deprecation")
+ public void doLayout() {
+ // PERFORM APPOINTMENT LAYOUT NOW
+ final Date date = (Date) calendarWidget.getDate().clone();
+
+ if (getSettings().isMultidayVisible()) {
+ multiViewBody.setDays((Date) date.clone(), calendarWidget.getDays());
+ }
+
+ dayViewHeader.setDays((Date) date.clone(), calendarWidget.getDays());
+ dayViewHeader.setYear((Date) date.clone());
+ dayViewBody.setDays((Date) date.clone(), calendarWidget.getDays());
+ dayViewBody.getTimeline().prepare();
+
+ dropController.setColumns(calendarWidget.getDays());
+ dropController.setIntervalsPerHour(calendarWidget.getSettings().getIntervalsPerHour());
+ dropController.setDayStartsAt(getSettings().getDayStartsAt());
+ dropController.setDate((Date)calendarWidget.getDate().clone());
+ dropController.setSnapSize(
+ calendarWidget.getSettings().getPixelsPerInterval());
+ dropController.setMaxProxyHeight(getMaxProxyHeight());
+ resizeController.setIntervalsPerHour(
+ calendarWidget.getSettings().getIntervalsPerHour());
+ resizeController.setDayStartsAt(getSettings().getDayStartsAt());
+ resizeController.setSnapSize(
+ calendarWidget.getSettings().getPixelsPerInterval());
+ proxyResizeController.setSnapSize(calendarWidget.getSettings().getPixelsPerInterval());
+ proxyResizeController.setIntervalsPerHour(calendarWidget.getSettings().getIntervalsPerHour());
+ proxyResizeController.setDayStartsAt(getSettings().getDayStartsAt());
+
+ this.selectedAppointmentWidgets.clear();
+ appointmentWidgets.clear();
+
+ // HERE IS WHERE WE DO THE LAYOUT
+ Date startDate = (Date) calendarWidget.getDate().clone();
+ Date endDate = (Date) calendarWidget.getDate().clone();
+ endDate.setDate(endDate.getDate() + 1);
+ DateUtils.resetTime(startDate);
+ DateUtils.resetTime(endDate);
+
+ startDate.setHours(startDate.getHours());
+ endDate.setHours(endDate.getHours());
+
+ for (int i = 0; i < calendarWidget.getDays(); i++) {
+
+ List filteredList = AppointmentUtil
+ .filterListByDate(calendarWidget.getAppointments(), startDate, endDate);
+
+ // perform layout
+ List appointmentAdapters = layoutStrategy
+ .doLayout(filteredList, i, calendarWidget.getDays());
+
+ // add all appointments back to the grid
+ addAppointmentsToGrid(appointmentAdapters, false);
+
+ startDate.setDate(startDate.getDate() + 1);
+ endDate.setDate(endDate.getDate() + 1);
+ }
+
+ List filteredList =
+ AppointmentUtil.filterListByDateRange(calendarWidget.getAppointments(),
+ calendarWidget.getDate(), calendarWidget.getDays());
+
+ ArrayList adapterList = new ArrayList();
+ int desiredHeight = layoutStrategy.doMultiDayLayout(filteredList,
+ adapterList, calendarWidget.getDate(), calendarWidget.getDays());
+
+ if (getSettings().isMultidayVisible()) {
+ multiViewBody.grid.setHeight(desiredHeight + "px");
+
+ addAppointmentsToGrid(adapterList, true);
+ }
+ }
+
+ /**
+ * Adds the Appointments to the view.
+ * @param appointmentList List of Appointments
+ * @param addToMultiView true if is adding the appointments to the multiview section, false otherwise
+ */
+ private void addAppointmentsToGrid(final List appointmentList, final boolean addToMultiView) {
+ for (AppointmentAdapter appt : appointmentList) {
+ AppointmentWidget panel = new AppointmentWidget();
+ panel.setWidth(appt.getWidth());
+ panel.setHeight(appt.getHeight());
+ panel.setTitle(appt.getAppointment().getTitle());
+ panel.setTop(appt.getTop());
+ panel.setLeft(appt.getLeft());
+ panel.setAppointment(appt.getAppointment());
+
+ boolean selected = calendarWidget.isTheSelectedAppointment(panel
+ .getAppointment());
+ if (selected) {
+ selectedAppointmentWidgets.add(panel);
+ }
+ styleManager.applyStyle(panel, selected);
+ appointmentWidgets.add(panel);
+
+ if (addToMultiView) {
+ panel.setMultiDay(true);
+ this.multiViewBody.grid.add(panel);
+ } else {
+ panel.setDescription(appt.getAppointment().getDescription());
+ dayViewBody.getGrid().grid.add(panel);
+
+ //make footer 'draggable'
+ if (calendarWidget.getSettings().isEnableDragDrop() && !appt.getAppointment().isReadOnly()) {
+ resizeController.makeDraggable(panel.getResizeHandle());
+ dragController.makeDraggable(panel, panel.getMoveHandle());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void scrollToHour(final int hour) {
+ dayViewBody.getScrollPanel().setVerticalScrollPosition((hour - getSettings().getDayStartsAt()) *
+ getSettings().getIntervalsPerHour() * getSettings().getPixelsPerInterval());
+ }
+
+ public void doSizing() {
+
+ if (calendarWidget.getOffsetHeight() > 0) {
+ if (getSettings().isMultidayVisible()) {
+ dayViewBody.setHeight(calendarWidget.getOffsetHeight() - 2
+ - dayViewHeader.getOffsetHeight()
+ - multiViewBody.getOffsetHeight() + "px");
+ } else {
+ dayViewBody.setHeight(calendarWidget.getOffsetHeight() - 2
+ - dayViewHeader.getOffsetHeight() + "px");
+ }
+ }
+ }
+
+ public void onDeleteKeyPressed() {
+ if (calendarWidget.getSelectedAppointment() != null) {
+ calendarWidget.fireDeleteEvent(calendarWidget.getSelectedAppointment());
+ }
+ }
+
+ public void onDoubleClick(Element element, Event event) {
+
+ List list = findAppointmentWidgetsByElement(element);
+ if (!list.isEmpty()) {
+ Appointment appt = list.get(0).getAppointment();
+ //if (appt.equals(calendarWidget.getSelectedAppointment()))
+ calendarWidget.fireOpenEvent(appt);
+ // exit out
+ } else if (getSettings().getTimeBlockClickNumber() == Click.Double
+ && element == dayViewBody.getGrid().gridOverlay.getElement()) {
+ int x = DOM.eventGetClientX(event) + Window.getScrollLeft();
+ int y = DOM.eventGetClientY(event) + Window.getScrollTop();
+ timeBlockClick(x, y);
+ }
+ }
+
+ public void onSingleClick(final Element element, final Event event) {
+
+ // Ignore the scroll panel
+ if (dayViewBody.getScrollPanel().getElement().equals(element)) {
+ return;
+ }
+
+ Appointment appointment = findAppointmentByElement(element);
+
+ if (appointment != null) {
+ selectAppointment(appointment);
+ } else if ((getSettings().getTimeBlockClickNumber() == Click.Single
+ || getSettings().getTimeBlockClickNumber() == Click.Drag)
+ && element == dayViewBody.getGrid().gridOverlay
+ .getElement()) {
+ int x = DOM.eventGetClientX(event) + Window.getScrollLeft();
+ int y = DOM.eventGetClientY(event) + Window.getScrollTop();
+ timeBlockClick(x, y);
+ }
+ }
+
+ public void onMouseOver(final Element element, final Event event) {
+ Appointment appointment = findAppointmentByElement(element);
+ calendarWidget.fireMouseOverEvent(appointment, element);
+ }
+
+ @Override
+ public void onAppointmentSelected(Appointment appointment) {
+
+ List clickedAppointmentAdapters =
+ findAppointmentWidget(appointment);
+
+ if (!clickedAppointmentAdapters.isEmpty()) {
+ for (AppointmentWidget adapter : selectedAppointmentWidgets) {
+ styleManager.applyStyle(adapter, false);
+ }
+
+ for (AppointmentWidget adapter : clickedAppointmentAdapters) {
+ styleManager.applyStyle(adapter, true);
+ }
+
+ selectedAppointmentWidgets.clear();
+ selectedAppointmentWidgets = clickedAppointmentAdapters;
+
+ float height = clickedAppointmentAdapters.get(0).getHeight();
+ // scrollIntoView ONLY if the appointment fits in the viewable area
+ if (dayViewBody.getScrollPanel().getOffsetHeight() > height) {
+ DOM.scrollIntoView(clickedAppointmentAdapters.get(0).getElement());
+ }
+ }
+ }
+
+ public void onRightArrowKeyPressed() {
+ calendarWidget.selectNextAppointment();
+ }
+
+ public void onUpArrowKeyPressed() {
+ calendarWidget.selectPreviousAppointment();
+ }
+
+ public void onDownArrowKeyPressed() {
+ calendarWidget.selectNextAppointment();
+ }
+
+ public void onLeftArrowKeyPressed() {
+ calendarWidget.selectPreviousAppointment();
+ }
+
+ @Override
+ public String getStyleName() {
+ return "gwt-cal";
+ }
+
+ @Override
+ public void attach(CalendarWidget widget) {
+ super.attach(widget);
+
+ if (dayViewBody == null) {
+ dayViewBody = new DayViewBody(this);
+ dayViewHeader = new DayViewHeader(this);
+ layoutStrategy = new DayViewLayoutStrategy(this);
+ if (getSettings().isMultidayVisible()) {
+ multiViewBody = new DayViewMultiDayBody(this);
+ }
+ }
+
+ calendarWidget.getRootPanel().add(dayViewHeader);
+ if (getSettings().isMultidayVisible()) {
+ calendarWidget.getRootPanel().add(multiViewBody);
+ }
+ calendarWidget.getRootPanel().add(dayViewBody);
+
+ if (getSettings() != null) {
+ scrollToHour(getSettings().getScrollToHour());
+ }
+
+ // Creates the different Controllers, if needed
+ createDragController();
+ createDropController();
+ createResizeController();
+ }
+
+ private void createDragController() {
+ if (dragController == null) {
+ dragController = new DayViewPickupDragController(dayViewBody.getGrid().grid, false);
+ dragController.setBehaviorDragProxy(true);
+ dragController.setBehaviorDragStartSensitivity(1);
+ dragController.setBehaviorConstrainedToBoundaryPanel(true); //do I need these?
+ dragController.setConstrainWidgetToBoundaryPanel(true); //do I need these?
+ dragController.setBehaviorMultipleSelection(false);
+ dragController.addDragHandler(new DragHandler(){
+
+ public void onDragEnd(DragEndEvent event) {
+ Appointment appt = ((AppointmentWidget) event.getContext().draggable).getAppointment();
+ calendarWidget.setCommittedAppointment(appt);
+ calendarWidget.fireUpdateEvent(appt);
+ }
+
+ public void onDragStart(DragStartEvent event) {
+ Appointment appt = ((AppointmentWidget) event.getContext().draggable).getAppointment();
+ calendarWidget.setRollbackAppointment(appt.clone());
+ ((DayViewPickupDragController)dragController).setMaxProxyHeight(getMaxProxyHeight());
+ }
+
+ public void onPreviewDragEnd(DragEndEvent event)
+ throws VetoDragException {
+ }
+
+ public void onPreviewDragStart(DragStartEvent event)
+ throws VetoDragException {
+ }
+ });
+ }
+ }
+
+ private void createDropController() {
+ if (dropController==null) {
+ dropController = new DayViewDropController(dayViewBody.getGrid().grid);
+ dragController.registerDropController(dropController);
+ }
+ }
+
+ private void createResizeController() {
+ if (resizeController == null) {
+ resizeController = new DayViewResizeController(dayViewBody.getGrid().grid);
+ resizeController.addDragHandler(new DragHandler(){
+
+ public void onDragEnd(DragEndEvent event) {
+ Appointment appt = ((AppointmentWidget) event.getContext().draggable.getParent()).getAppointment();
+ calendarWidget.setCommittedAppointment(appt);
+ calendarWidget.fireUpdateEvent(appt);
+ }
+
+ public void onDragStart(DragStartEvent event) {
+ calendarWidget
+ .setRollbackAppointment(((AppointmentWidget) event
+ .getContext().draggable.getParent()).getAppointment()
+ .clone());
+ }
+
+ public void onPreviewDragEnd(DragEndEvent event)
+ throws VetoDragException {}
+ public void onPreviewDragStart(DragStartEvent event)
+ throws VetoDragException {}
+ });
+ }
+
+ if(proxyResizeController == null) {
+ proxyResizeController = new DayViewResizeController(dayViewBody.getGrid().grid);
+ proxyResizeController.addDragHandler(new DragHandler(){
+ long startTime = 0L;
+ int initialX = 0;
+ int initialY = 0;
+ Date startDate;
+
+ public void onDragEnd(DragEndEvent event) {
+ long clickTime = System.currentTimeMillis() - startTime;
+ int y = event.getContext().mouseY;
+ if (clickTime <= 500 && initialY == y) {
+ calendarWidget.fireTimeBlockClickEvent(startDate);
+ } else {
+ Appointment appt = ((AppointmentWidget) event.getContext().draggable.getParent()).getAppointment();
+ calendarWidget.setCommittedAppointment(appt);
+ calendarWidget.fireCreateEvent(appt);
+ }
+ }
+
+ public void onDragStart(DragStartEvent event) {
+ startTime = System.currentTimeMillis();
+ initialX = event.getContext().mouseX;
+ initialY = event.getContext().mouseY;
+ startDate = getCoordinatesDate(initialX, initialY);
+ calendarWidget.setRollbackAppointment(null);
+ }
+
+ public void onPreviewDragEnd(DragEndEvent event)
+ throws VetoDragException {}
+ public void onPreviewDragStart(DragStartEvent event)
+ throws VetoDragException {}
+ });
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private Date getCoordinatesDate(int x, int y) {
+ int left = dayViewBody.getGrid().gridOverlay.getAbsoluteLeft();
+ int top = dayViewBody.getScrollPanel().getAbsoluteTop();
+ int width = dayViewBody.getGrid().gridOverlay.getOffsetWidth();
+ int scrollOffset = dayViewBody.getScrollPanel().getVerticalScrollPosition();
+
+ // x & y are based on screen position,need to get x/y relative to
+ // component
+ int relativeY = y - top + scrollOffset;
+ int relativeX = x - left;
+
+ // find the interval clicked and day clicked
+ double interval = Math.floor(relativeY
+ / (double) getSettings().getPixelsPerInterval());
+ double day = Math.floor((double) relativeX
+ / ((double) width / (double) calendarWidget.getDays()));
+
+ // create new appointment date based on click
+ Date newStartDate = calendarWidget.getDate();
+ newStartDate.setHours(getSettings().getDayStartsAt());
+ newStartDate.setMinutes(0);
+ newStartDate.setSeconds(0);
+ newStartDate.setMinutes((int) interval
+ * (60 / getSettings().getIntervalsPerHour()));
+ newStartDate.setDate(newStartDate.getDate() + (int) day);
+
+ return newStartDate;
+ }
+
+ private void timeBlockClick(int x, int y) {
+ int left = dayViewBody.getGrid().gridOverlay.getAbsoluteLeft();
+ int top = dayViewBody.getScrollPanel().getAbsoluteTop();
+ int width = dayViewBody.getGrid().gridOverlay.getOffsetWidth();
+ int scrollOffset = dayViewBody.getScrollPanel().getVerticalScrollPosition();
+
+ // x & y are based on screen position,need to get x/y relative to
+ // component
+ int relativeY = y - top + scrollOffset;
+ int relativeX = x - left;
+
+ // find the interval clicked and day clicked
+ double day = Math.floor((double) relativeX
+ / ((double) width / (double) calendarWidget.getDays()));
+
+ Date newStartDate = getCoordinatesDate(x, y);
+
+ if (getSettings().getTimeBlockClickNumber() != Click.Drag) {
+ calendarWidget.fireTimeBlockClickEvent(newStartDate);
+ } else {
+ int snapSize = calendarWidget.getSettings().getPixelsPerInterval();
+ // Create the proxy
+ width = width / calendarWidget.getDays();
+ left = (int) day * width;
+ // Adjust the start to the closest interval
+ top = (int)Math.floor(
+ (float) relativeY / snapSize) * snapSize;
+
+ AppointmentWidget proxy = new AppointmentWidget();
+ Appointment app = new Appointment();
+ app.setStart(newStartDate);
+ app.setEnd(newStartDate);
+ proxy.setAppointment(app);
+ proxy.setStart(newStartDate);
+ proxy.setPixelSize(width, /*height*/ snapSize);
+ dayViewBody.getGrid().grid.add(proxy, left, top);
+ styleManager.applyStyle(proxy, false);
+ proxyResizeController.makeDraggable(proxy.getResizeHandle());
+
+ NativeEvent evt = Document.get().createMouseDownEvent(1, 0, 0, x, y, false,
+ false, false, false, NativeEvent.BUTTON_LEFT);
+ proxy.getResizeHandle().getElement().dispatchEvent(evt);
+ }
+ }
+
+ private List findAppointmentWidgetsByElement(
+ Element element) {
+ return findAppointmentWidget(findAppointmentByElement(element));
+ }
+
+ /**
+ * Returns the {@link Appointment} indirectly associated to the passed
+ * element. Each Appointment drawn on the CalendarView maps to
+ * a Widget and therefore an Element. This method attempts to find an
+ * Appointment based on the provided Element. If no match is found a null
+ * value is returned.
+ *
+ * @param element
+ * Element to look up.
+ * @return Appointment matching the element.
+ */
+ private Appointment findAppointmentByElement(Element element) {
+ Appointment appointmentAtElement = null;
+ for (AppointmentWidget widget : appointmentWidgets) {
+ if (DOM.isOrHasChild(widget.getElement(), element)) {
+ appointmentAtElement = widget.getAppointment();
+ break;
+ }
+ }
+ return appointmentAtElement;
+ }
+
+ /**
+ * Finds any related adapters that match the given Appointment.
+ *
+ * @param appt
+ * Appointment to match.
+ * @return List of related AppointmentWidget objects.
+ */
+ private List findAppointmentWidget(Appointment appt) {
+ ArrayList appointmentAdapters = new ArrayList();
+ if (appt != null) {
+ for (AppointmentWidget widget : appointmentWidgets) {
+ if (widget.getAppointment().equals(appt)) {
+ appointmentAdapters.add(widget);
+ }
+ }
+ }
+ return appointmentAdapters;
+ }
+
+ public HandlerRegistration addWeekSelectionHandler(
+ WeekSelectionHandler handler) {
+ return dayViewHeader.addWeekSelectionHandler(handler);
+ }
+
+ public HandlerRegistration addDaySelectionHandler(
+ DaySelectionHandler handler) {
+ return dayViewHeader.addDaySelectionHandler(handler);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewBody.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewBody.java
new file mode 100644
index 0000000..3083c43
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewBody.java
@@ -0,0 +1,100 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see = workingHourStart && (i + dayStartsAt) <= workingHourStop);
+ //create major interval
+ SimplePanel sp1 = new SimplePanel();
+ sp1.setStyleName("major-time-interval");
+ sp1.setHeight(intervalSize + FormattingUtil.getBorderOffset() + "px");
+
+ //if working hours set
+ if (isWorkingHours) {
+ sp1.addStyleName("working-hours");
+ }
+
+ //add to body
+ grid.add(sp1);
+
+ for (int x = 0; x < intervalsPerHour - 1; x++) {
+ SimplePanel sp2 = new SimplePanel();
+ sp2.setStyleName("minor-time-interval");
+
+ sp2.setHeight(intervalSize + FormattingUtil.getBorderOffset() + "px");
+ if (isWorkingHours) {
+ sp2.addStyleName("working-hours");
+ }
+ grid.add(sp2);
+ }
+ }
+
+ for (int day = 0; day < days; day++) {
+ dayLeft = dayWidth * day;
+ SimplePanel dayPanel = new SimplePanel();
+ dayPanel.setStyleName("day-separator");
+ grid.add(dayPanel);
+ DOM.setStyleAttribute(dayPanel.getElement(), "left", dayLeft + "%");
+ }
+
+ gridOverlay.setHeight("100%");
+ gridOverlay.setWidth("100%");
+ DOM.setStyleAttribute(gridOverlay.getElement(), "position", "absolute");
+ DOM.setStyleAttribute(gridOverlay.getElement(), "left", "0px");
+ DOM.setStyleAttribute(gridOverlay.getElement(), "top", "0px");
+ grid.add(gridOverlay);
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewHeader.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewHeader.java
new file mode 100644
index 0000000..0e8a263
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewHeader.java
@@ -0,0 +1,220 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see , HasDaySelectionHandlers {
+ private FlexTable header = new FlexTable();
+ private VerticalPanel timePanel = new VerticalPanel();
+ private AbsolutePanel dayPanel = new AbsolutePanel();
+ private AbsolutePanel weekPanel = new AbsolutePanel();
+ private AbsolutePanel splitter = new AbsolutePanel();
+ private static final String GWT_CALENDAR_HEADER_STYLE = "gwt-calendar-header";
+ private static final String DAY_CELL_CONTAINER_STYLE = "day-cell-container";
+ private static final String WEEK_CELL_CONTAINER_STYLE = "week-cell-container";
+ private static final String YEAR_CELL_STYLE = "year-cell";
+ private static final String SPLITTER_STYLE = "splitter";
+ private final boolean showWeekNumbers;
+ private final HasSettings settings;
+
+ public DayViewHeader(HasSettings settings) {
+ initWidget(header);
+
+ this.settings = settings;
+
+ header.setStyleName(GWT_CALENDAR_HEADER_STYLE);
+ dayPanel.setStyleName(DAY_CELL_CONTAINER_STYLE);
+ weekPanel.setStyleName(WEEK_CELL_CONTAINER_STYLE);
+ timePanel.setWidth("100%");
+
+ showWeekNumbers = settings.getSettings().isShowingWeekNumbers();
+
+ header.insertRow(0);
+ header.insertRow(0);
+ header.insertCell(0, 0);
+ header.insertCell(0, 0);
+ header.insertCell(0, 0);
+ header.setWidget(0, 1, timePanel);
+ header.getCellFormatter().setStyleName(0, 0, YEAR_CELL_STYLE);
+ header.getCellFormatter().setWidth(0, 2,
+ WindowUtils.getScrollBarWidth(true) + "px");
+
+ header.getFlexCellFormatter().setColSpan(1, 0, 3);
+ header.setCellPadding(0);
+ header.setBorderWidth(0);
+ header.setCellSpacing(0);
+
+ if (showWeekNumbers) {
+ timePanel.add(weekPanel);
+ }
+ timePanel.add(dayPanel);
+
+ splitter.setStylePrimaryName(SPLITTER_STYLE);
+ header.setWidget(1, 0, splitter);
+ }
+
+ public void setDays(Date date, int days) {
+
+ dayPanel.clear();
+ weekPanel.clear();
+
+ float dayWidth = 100f / days;
+ float dayLeft;
+ int week = DateUtils.calendarWeekIso(date);
+ int previousDayWeek = week;
+ Date previousDate = date;
+ float weekWidth = 0f;
+ float weekLeft = 0f;
+
+ for (int i = 0; i < days; i++) {
+
+ // set the left position of the day splitter to
+ // the width * incremented value
+ dayLeft = dayWidth * i;
+
+ // increment the date by 1
+ if (i > 0) {
+ DateUtils.moveOneDayForward(date);
+ } else {
+ // initialize the week values
+ weekLeft = dayLeft;
+ weekWidth = dayWidth;
+ }
+
+ String headerTitle = CalendarFormat.INSTANCE.getDateFormat().format(date);
+
+ Label dayLabel = new Label();
+ dayLabel.setStylePrimaryName("day-cell");
+ dayLabel.setWidth(dayWidth + "%");
+ dayLabel.setText(headerTitle);
+ DOM.setStyleAttribute(dayLabel.getElement(), "left", dayLeft + "%");
+
+ addDayClickHandler(dayLabel, (Date) date.clone());
+
+ boolean found = false;
+ for (Date day : settings.getSettings().getHolidays()) {
+ if (DateUtils.areOnTheSameDay(day, date)) {
+ dayLabel.setStyleName("day-cell-holiday");
+ found = true;
+ break;
+ }
+ }
+
+ // set the style of the header to show that it is today
+ if (DateUtils.areOnTheSameDay(new Date(), date)) {
+ dayLabel.setStyleName("day-cell-today");
+ } else if (!found && DateUtils.isWeekend(date)) {
+ dayLabel.setStyleName("day-cell-weekend");
+ }
+
+ if (showWeekNumbers) {
+ week = DateUtils.calendarWeekIso(date);
+ boolean lastDay = i + 1 == days;
+ if ((previousDayWeek != week) || lastDay) {
+ if (lastDay) {
+ previousDayWeek = week;
+ previousDate = date;
+ }
+ String weekTitle = "W " + previousDayWeek;
+
+ Label weekLabel = new Label();
+ weekLabel.setStylePrimaryName("week-cell");
+ weekLabel.setWidth(weekWidth + "%");
+ weekLabel.setText(weekTitle);
+ DOM.setStyleAttribute(weekLabel.getElement(), "left", weekLeft + "%");
+
+ addWeekClickHandler(weekLabel, previousDate);
+
+ weekPanel.add(weekLabel);
+
+ weekWidth = dayWidth;
+ weekLeft = dayLeft + dayWidth;
+ } else {
+ weekWidth += dayWidth;
+ }
+ previousDayWeek = week;
+ previousDate = date;
+ }
+
+ dayPanel.add(dayLabel);
+ }
+ }
+
+ public void setYear(Date date) {
+ setYear(DateUtils.year(date));
+ }
+
+ public void setYear(int year) {
+ header.setText(0, 0, String.valueOf(year));
+ }
+
+ private void addDayClickHandler(final Label dayLabel, final Date day) {
+ dayLabel.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ fireSelectedDay(day);
+ }
+ });
+ }
+
+ private void addWeekClickHandler(final Label weekLabel, final Date day) {
+ weekLabel.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ fireSelectedWeek(day);
+ }
+ });
+ }
+
+ private void fireSelectedDay(final Date day) {
+ DaySelectionEvent.fire(this, day);
+ }
+
+ private void fireSelectedWeek(final Date day) {
+ WeekSelectionEvent.fire(this, day);
+ }
+
+ public HandlerRegistration addWeekSelectionHandler(
+ WeekSelectionHandler handler) {
+ return addHandler(handler, WeekSelectionEvent.getType());
+ }
+
+ public HandlerRegistration addDaySelectionHandler(
+ DaySelectionHandler handler) {
+ return addHandler(handler, DaySelectionEvent.getType());
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewLayoutStrategy.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewLayoutStrategy.java
new file mode 100644
index 0000000..e43e49a
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewLayoutStrategy.java
@@ -0,0 +1,439 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ * Note how overlapping appointments are displayed in the provided image
+ *
+ * @author Brad Rydzewski
+ * @version 1.0 6/07/09
+ * @since 1.0
+ */
+public class DayViewLayoutStrategy {
+
+ private static final int MINUTES_PER_HOUR = 60;
+ private static final int HOURS_PER_DAY = 24;
+ private final HasSettings settings;
+
+ public DayViewLayoutStrategy(final HasSettings settings) {
+ this.settings = settings;
+ }
+
+ public List doLayout(List appointments, int dayIndex, int dayCount) {
+
+ int intervalsPerHour = settings.getSettings().getIntervalsPerHour(); //30 minute intervals
+ float intervalSize = settings.getSettings().getPixelsPerInterval(); //25 pixels per interval
+
+ /*
+ * Note: it is important that all appointments are sorted by Start date
+ * (asc) and Duration (desc) for this algorithm to work. If that is not
+ * the case, it won't work, at all!! Maybe this is a problem that needs
+ * to be addressed
+ */
+
+ // set to 30 minutes. this means there will be 48 cells. 60min / 30min
+ // interval * 24
+ //int minutesPerInterval = 30;
+ // interval size, set to 100px
+ //float sizeOfInterval = 25f;
+
+ // a calendar can view multiple days at a time. sets number of visible
+ // days
+ // TODO: use this later, not currently implemented
+ // float numberOfDays = dates.size();
+
+ int minutesPerInterval = MINUTES_PER_HOUR / intervalsPerHour;
+
+ // get number of cells (time blocks)
+ int numberOfTimeBlocks = MINUTES_PER_HOUR / minutesPerInterval * HOURS_PER_DAY;
+ TimeBlock[] timeBlocks = new TimeBlock[numberOfTimeBlocks];
+
+ for (int i = 0; i < numberOfTimeBlocks; i++) {
+ TimeBlock t = new TimeBlock();
+ t.setStart(i * minutesPerInterval);
+ t.setEnd(t.getStart() + minutesPerInterval);
+ t.setOrder(i);
+ t.setTop((float) i * intervalSize);
+ t.setBottom(t.getTop() + intervalSize);
+ timeBlocks[i] = t;
+ }
+
+ // each appointment will get "wrapped" in an appoinetment cell object,
+ // so that we can assign it a location in the grid, row and
+ // column span, etc.
+ ArrayList appointmentCells = new ArrayList();
+ // Map blockGroup = new
+ // HashMap();
+ int groupMaxColumn = 0; // track total columns here! this will reset
+ // when a group completes
+ int groupStartIndex = -1;
+ int groupEndIndex = -2;
+
+ // Question: how to distinguish start / finish of a new group?
+ // Answer: when endCell of previous appointments < startCell of new
+ // appointment
+
+ // for each appointments, we need to see if it intersects with each time
+ // block
+ for (Appointment appointment : appointments) {
+
+ TimeBlock startBlock = null;
+ TimeBlock endBlock = null;
+
+ // if(blockGroupEndCell)
+
+ // wrap appointment with AppointmentInterface Cell and add to list
+ AppointmentAdapter apptCell = new AppointmentAdapter(appointment);
+ appointmentCells.add(apptCell);
+
+ // get the first time block in which the appointment should appear
+ // TODO: since appointments are sorted, we should never need to
+ // re-evaluate a time block that had zero matches...
+ // store the index of the currently evaluated time block, if no
+ // match, increment
+ // that will prevent the same block from ever being re-evaluated
+ // after no match found
+ for (TimeBlock block : timeBlocks) {
+ // does the appointment intersect w/ the block???
+ if (block.intersectsWith(apptCell)) {
+
+ // we found one! set as start block and exit loop
+ startBlock = block;
+ // blockGroup.put(block, block);
+
+ if (groupEndIndex < startBlock.getOrder()) {
+
+ //System.out.println(" prior group max cols: "
+ // + groupMaxColumn);
+
+ for (int i = groupStartIndex; i <= groupEndIndex; i++) {
+
+ TimeBlock tb = timeBlocks[i];
+ tb.setTotalColumns(groupMaxColumn + 1);
+ //System.out.println(" total col set for block: "
+ // + i);
+ }
+ groupStartIndex = startBlock.getOrder();
+ //System.out.println("new group at: " + groupStartIndex);
+ groupMaxColumn = 0;
+ }
+
+ break;
+ } else {
+ // here is where I would increment, as per above to-do
+ }
+ }
+
+ // add the appointment to the start block
+ startBlock.getAppointments().add(apptCell);
+ // add block to appointment
+ apptCell.getIntersectingBlocks().add(startBlock);
+
+ // set the appointments column, if it has not already been set
+ // if it has been set, we need to get it for reference later on in
+ // this method
+ int column = startBlock.getFirstAvailableColumn();
+ apptCell.setColumnStart(column);
+ apptCell.setColumnSpan(1); // hard-code to 1, for now
+
+ // we track the max column for a time block
+ // if a column get's added make sure we increment
+ // if (startBlock.getTotalColumns() <= column) {
+ // startBlock.setTotalColumns(column+1);
+ // }
+
+ // add column to block's list of occupied columns, so that the
+ // column cannot be given to another appointment
+ startBlock.getOccupiedColumns().put(column, column);
+
+ // sets the start cell of the appt to the current block
+ // we can do this since the blocks are ordered ascending
+ apptCell.setCellStart(startBlock.getOrder());
+
+ // go through all subsequent blocks...
+ // find intersections
+ for (int i = startBlock.getOrder() + 1; i < timeBlocks.length; i++) {
+
+ // get the nextTimeBlock
+ TimeBlock nextBlock = timeBlocks[i];
+
+ // exit look if end date > block start, since no subsequent
+ // blocks will ever intersect
+ // if (apptCell.getAppointmentEnd() > nextBlock.getStart()) {
+ // break; //does appt intersect with this block?
+ // }
+ if (nextBlock.intersectsWith(apptCell)) {
+
+ // yes! add appointment to the block
+ // register start column
+ nextBlock.getAppointments().add(apptCell);
+ nextBlock.getOccupiedColumns().put(column, column);
+ endBlock = nextBlock; // this may change if intersects with
+ // next block
+
+ // add block to appointments list of intersecting blocks
+ apptCell.getIntersectingBlocks().add(nextBlock);
+
+ // we track the max column for a time block
+ // if a column get's added make sure we increment
+ // if (nextBlock.getTotalColumns() <= column) {
+ // nextBlock.setTotalColumns(column+1);
+ // }
+
+ // blockGroup.put(nextBlock, nextBlock);
+ }
+ }
+
+ // if end block was never set, use the start block
+ endBlock = (endBlock == null) ? startBlock : endBlock;
+ // maybe here is the "end" of a group, where we then evaluate max
+ // column
+
+ if (column > groupMaxColumn) {
+ groupMaxColumn = column;
+ // System.out.println(" max col: " + groupMaxColumn);
+ }
+
+ if (groupEndIndex < endBlock.getOrder()) {
+ groupEndIndex = endBlock.getOrder();
+ //System.out.println(" end index (re)set: " + groupEndIndex);
+ }
+ // for(int i = groupStartIndex; i<=groupEndIndex; i++) {
+ // timeBlocks[i].setTotalColumns(groupMaxColumn);
+ // }
+ // groupMaxColumn=1;
+ // }
+
+ // for(TimeBlock timeBlock : blockGroup.values()) {
+ //
+ // }
+
+ // blockGroup = new HashMap();
+
+ // set the appointments cell span (top to bottom)
+ apptCell.setCellSpan(endBlock.getOrder() - startBlock.getOrder() + 1);
+
+ }
+ for (int i = groupStartIndex; i <= groupEndIndex; i++) {
+
+ TimeBlock tb = timeBlocks[i];
+ tb.setTotalColumns(groupMaxColumn + 1);
+ //System.out.println(" total col set for block: " + i);
+ }
+ // we need to know the MAX number of cells for each time block.
+ // so unfortunately we have to go back through the list to find this out
+ /*
+ * for(AppointmentCell apptCell : appointmentCells) {
+ *
+ * for (TimeBlock block : apptCell.getIntersectingBlocks()) {
+ *
+ * int maxCol = 0;
+ *
+ * //find the max cell for (AppointmentCell apptCell :
+ * block.getAppointments()) { int col = apptCell.getColumnStart(); if
+ * (col > maxCol) { maxCol = col; } }
+ *
+ * block.setTotalColumns(maxCol+1); } }
+ */
+
+
+ //last step is to calculate the adjustment reuired for 'multi-day' / multi-column
+ float widthAdj = 1f / dayCount;
+
+ float paddingLeft = .5f;
+ float paddingRight = .5f;
+ float paddingBottom = 2;
+
+ // now that everything has been assigned a cell, column and spans
+ // we can calculate layout
+ // Note: this can only be done after every single appointment has
+ // been assigned a position in the grid
+ for (AppointmentAdapter apptCell : appointmentCells) {
+
+ float width = 1f / (float) apptCell.getIntersectingBlocks().get(0).getTotalColumns() * 100;
+ float left = (float) apptCell.getColumnStart() / (float) apptCell.getIntersectingBlocks().get(0).getTotalColumns() * 100;
+
+ //AppointmentInterface appt = apptCell.getAppointment();
+ apptCell.setTop((float) apptCell.getCellStart() * intervalSize); // ok!
+ apptCell.setLeft((widthAdj * 100 * dayIndex) + (left * widthAdj) + paddingLeft); // ok
+ apptCell.setWidth(width * widthAdj - paddingLeft - paddingRight); // ok!
+ apptCell.setHeight((float) apptCell.getIntersectingBlocks().size() * ((float) intervalSize) - paddingBottom); // ok!
+
+ float apptStart = apptCell.getAppointmentStart();
+ float apptEnd = apptCell.getAppointmentEnd();
+ float blockStart = timeBlocks[apptCell.getCellStart()].getStart();
+ float blockEnd = timeBlocks[apptCell.getCellStart() + apptCell.getCellSpan() - 1].getEnd();
+ float blockDuration = blockEnd - blockStart;
+ float apptDuration = apptEnd - apptStart;
+ float timeFillHeight = apptDuration / blockDuration * 100f;
+ float timeFillStart = (apptStart - blockStart) / blockDuration * 100f;
+// System.out.println("apptStart: "+apptStart);
+// System.out.println("apptEnd: "+apptEnd);
+// System.out.println("blockStart: "+blockStart);
+// System.out.println("blockEnd: "+blockEnd);
+// System.out.println("timeFillHeight: "+timeFillHeight);
+ //System.out.println("timeFillStart: "+timeFillStart);
+ //System.out.println("------------");
+ apptCell.setCellPercentFill(timeFillHeight);
+ apptCell.setCellPercentStart(timeFillStart);
+ //appt.formatTimeline(apptCell.getCellPercentStart(), apptCell.getCellPercentFill());
+ }
+
+ return appointmentCells;
+ }
+
+ public int doMultiDayLayout(List appointments, List adapters, Date start, int days) {
+
+ //create array to hold all appointments for a particular day
+ //HashMap> appointmentDayMap
+ // = new HashMap>();
+
+ //for a particular day need to track all used rows
+ HashMap> daySlotMap = new HashMap>();
+
+ int minHeight = 30;
+ int maxRow = 0;
+
+
+ //convert appointment to adapter
+ for (Appointment appointment : appointments) {
+ adapters.add(new AppointmentAdapter(appointment));
+ }
+
+ //create array of dates
+ ArrayList dateList = new ArrayList();
+ Date tempStartDate = (Date)start.clone();
+
+// tempStartDate.setHours(0);
+// tempStartDate.setMinutes(0);
+// tempStartDate.setSeconds(0);
+ for (int i = 0; i < days; i++) {
+ Date d = (Date) tempStartDate.clone();
+ DateUtils.resetTime(d);
+ //appointmentDayMap.put(d, new ArrayList());
+ daySlotMap.put(i, new HashMap());
+ dateList.add(d);
+ DateUtils.moveOneDayForward(tempStartDate);
+ }
+
+
+ //add appointments to each day
+ for (AppointmentAdapter adapter : adapters) {
+
+ int columnSpan = 0; //number of columns spanned
+ boolean isStart = true; //indicates if current column is appointment start column
+
+ //set column & span
+ for (int i = 0; i < dateList.size(); i++) {
+ Date date = dateList.get(i);
+ boolean isWithinRange =
+ AppointmentUtil.rangeContains(
+ adapter.getAppointment(), date);
+
+ //System.out.println(" isWithinRange == " + isWithinRange + " for start: " + adapter.getAppointment().getStart() + " end: " + adapter.getAppointment().getEnd() + " date: "+date);
+
+ //while we are at it, we can set the adapters start column
+ // and colun span
+ if (isWithinRange) {
+ //appointmentDayMap.get(date).add(adapter);
+
+ if (isStart) {
+ adapter.setColumnStart(i);
+ isStart = false;
+ }
+
+ adapter.setColumnSpan(columnSpan);
+ columnSpan++;
+ }
+ }
+
+ //now we set the row, which cannot be more than total # of appointments
+ for (int x = 0; x < adapters.size(); x++) {
+
+ boolean isRowOccupied = false;
+ for (int y = adapter.getColumnStart(); y <= adapter.getColumnStart() + adapter.getColumnSpan(); y++) {
+ try{
+ HashMap rowMap = daySlotMap.get(y);
+ if (rowMap.containsKey(x)) {
+ isRowOccupied = true;
+ //System.out.println(" row [" + x+"] is occupied for day [" + y+"]");
+ } else {
+ //isRowOccupied = false;
+ break; //break out of loop, nothing found in row slot
+ }
+ }catch(Exception ex) {
+ //System.out.println("Exception: y=" + y + " x=" + x + " adapters.size=" + adapters.size() + " start="+adapter.getAppointment().getStart() + " end="+adapter.getAppointment().getEnd().toString());
+ }
+ }
+
+ if (!isRowOccupied) {
+ //add row to maps
+ for (int y = adapter.getColumnStart(); y <= adapter.getColumnStart() + adapter.getColumnSpan(); y++) {
+ HashMap rowMap = daySlotMap.get(y);
+ rowMap.put(x, x);
+ if (x > maxRow) {
+ maxRow = x;
+ }
+ //System.out.println(" > added "+ x + " to row list for column " + y);
+ }
+ //set the row (also named cell)
+ adapter.setCellStart(x);
+ //break loop
+
+
+ //now we set the appointment's location
+ //Appointment appt = adapter.getAppointment();
+ float top = adapter.getCellStart() * 25f + 5f;
+ float width = ((float) adapter.getColumnSpan() + 1f) / days * 100f - 1f; //10f=padding
+ float left = ((float) adapter.getColumnStart()) / days * 100f + .5f; //10f=padding
+ //float left = (float) adapter.getColumnStart() / (float) apptCell.getIntersectingBlocks().get(0).getTotalColumns() * 100;
+ adapter.setWidth(width);
+ adapter.setLeft(left);
+ adapter.setTop(top);
+ adapter.setHeight(20);
+ //System.out.println("set appointment [" + appt.getTitle() + "] layout left: " + left + " top: " + top + " width: " + width);
+ break;
+ }
+ }
+
+ //System.out.println("multi-day layout -- title: \"" + adapter.getAppointment().getTitle() + "\" | col start: " + adapter.getColumnStart() + " | colspan: " + adapter.getColumnSpan() + " | row: " + adapter.getCellStart() + " | start date: " + adapter.getAppointment().getStart() + " | end date: " + adapter.getAppointment().getEnd());
+
+
+ }
+
+ int height = (maxRow + 1) * 25 + 5;
+ return Math.max(height, minHeight);
+ }
+
+
+
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewMultiDayBody.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewMultiDayBody.java
new file mode 100644
index 0000000..8a303c0
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewMultiDayBody.java
@@ -0,0 +1,108 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see = CalendarFormat.HOURS_IN_DAY) {
+ index = index - CalendarFormat.HOURS_IN_DAY;
+ }
+ String hour = CalendarFormat.INSTANCE.getHourLabels()[index];
+ index++;
+ i++;
+
+ //block
+ SimplePanel hourWrapper = new SimplePanel();
+ hourWrapper.setStylePrimaryName(TIME_LABEL_STYLE);
+ hourWrapper.setHeight((labelHeight + FormattingUtil.getBorderOffset()) + "px");
+
+ FlowPanel flowPanel = new FlowPanel();
+ flowPanel.setStyleName("hour-layout");
+
+ String amPm = " ";
+
+ if (index < 13)
+ amPm += CalendarFormat.INSTANCE.getAm();
+ else if (index > 13)
+ amPm += CalendarFormat.INSTANCE.getPm();
+ else {
+ if (CalendarFormat.INSTANCE.isUseNoonLabel()) {
+ hour = CalendarFormat.INSTANCE.getNoon();
+ amPm = "";
+ } else {
+ amPm += CalendarFormat.INSTANCE.getPm();
+ }
+ }
+
+ Label hourLabel = new Label(hour);
+ hourLabel.setStylePrimaryName("hour-text");
+ flowPanel.add(hourLabel);
+
+ Label amPmLabel = new Label(amPm);
+ amPmLabel.setStylePrimaryName("ampm-text");
+ flowPanel.add(amPmLabel);
+
+ hourWrapper.add(flowPanel);
+
+ timelinePanel.add(hourWrapper);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/dayview/TimeBlock.java b/src/com/bradrydzewski/gwt/calendar/client/dayview/TimeBlock.java
new file mode 100644
index 0000000..3099bd2
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/dayview/TimeBlock.java
@@ -0,0 +1,148 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see A block of time can contain or be interested by one or many appointments.
+ * Using the above example, a time block from 10am - 10:30 am could contain the
+ * following appointments:
+ *
+ *
10:00 - 10:30, where start / end are same as time block
+ *
9:00 - 10:30, where the appointment starts before the time block
+ * starts but ends at same time as time block
+ *
9:00 - 10:20, where appointment start before
+ * and ends after start of time block
+ *
9:00 - 11:00, where appointment starts before and ends after the time
+ * block does
+ *
10:05 - 10:25, where appointment starts after but ends before the
+ * time block
+ * Above
+ * are example use cases of a time block and how it relates to an appointment.
+ * Note that an appointment can intersect with one ore many time blocks. This
+ * class holds a number of parameters allowing a time block to manage its
+ * appointments and determine where the appointments should be arranged within
+ * itself.
+ *
+ * @author Brad Rydzewski
+ */
+public class TimeBlock {
+ private List appointments = new ArrayList();
+ private Map occupiedColumns = new HashMap();
+ private int totalColumns = 1;
+ private int order;
+ private String name;
+ private int start;
+ private int end;
+ private float top;
+ private float bottom;
+
+ public List getAppointments() {
+ return appointments;
+ }
+
+ public Map getOccupiedColumns() {
+ return occupiedColumns;
+ }
+
+ public int getFirstAvailableColumn() {
+ int col = 0;
+ while (true) {
+ if (occupiedColumns.containsKey(col))
+ col++;
+ else return col;
+ }
+ }
+
+ public int getTotalColumns() {
+ return totalColumns;
+ }
+
+ public void setTotalColumns(int totalColumns) {
+ this.totalColumns = totalColumns;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ public void setEnd(int end) {
+ this.end = end;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ public float getBottom() {
+ return bottom;
+ }
+
+ public void setBottom(float bottom) {
+ this.bottom = bottom;
+ }
+
+ public float getTop() {
+ return top;
+ }
+
+ public void setTop(float top) {
+ this.top = top;
+ }
+
+ public boolean intersectsWith(int apptStart, int apptEnd) {
+ //scenario 1: start date of appt between start and end of block
+ if (apptStart >= this.getStart() && apptStart < this.getEnd())
+ return true;
+
+ //scenario 2: end date of appt > block start date &
+ // start date of appt before block start date
+ return apptEnd > this.getStart() && apptStart < this.getStart();
+ }
+
+ public boolean intersectsWith(AppointmentAdapter appt) {
+ return intersectsWith(appt.getAppointmentStart(),
+ appt.getAppointmentEnd());
+ }
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/CreateEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/CreateEvent.java
new file mode 100644
index 0000000..23b8221
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/CreateEvent.java
@@ -0,0 +1,107 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ private boolean cancelled = false;
+
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the target
+ */
+ public static boolean fire(HasUpdateHandlers source, T target) {
+ if (TYPE != null) {
+ CreateEvent event = new CreateEvent(target);
+ source.fireEvent(event);
+ return !event.isCancelled();
+ }
+ return true;
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T target;
+
+ /**
+ * Creates a new delete event.
+ *
+ * @param target the ui object being opened
+ */
+ protected CreateEvent(T target) {
+ this.target = target;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the target.
+ *
+ * @return the target
+ */
+ public T getTarget() {
+ return target;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(CreateHandler handler) {
+ handler.onCreate(this);
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/CreateHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/CreateHandler.java
new file mode 100644
index 0000000..392f085
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/CreateHandler.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface CreateHandler extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onCreate(CreateEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestEvent.java
new file mode 100644
index 0000000..7f8ce93
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestEvent.java
@@ -0,0 +1,104 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the target
+ */
+ public static void fire(HasDateRequestHandlers source, T target) {
+ fire(source, target, null);
+ }
+
+ public static void fire(HasDateRequestHandlers source, T target, Object widget) {
+ if (TYPE != null) {
+ DateRequestEvent event = new DateRequestEvent(target, widget);
+ source.fireEvent(event);
+ }
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T target;
+ private final Object clicked;
+
+ /**
+ * Creates a new delete event.
+ *
+ * @param target the ui object being opened
+ */
+ protected DateRequestEvent(T target, Object clicked) {
+ this.target = target;
+ this.clicked = clicked;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the target.
+ *
+ * @return the target
+ */
+ public T getTarget() {
+ return target;
+ }
+
+ public Object getClicked() {
+ return clicked;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(DateRequestHandler handler) {
+ handler.onDateRequested(this);
+ }
+
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestHandler.java
new file mode 100644
index 0000000..3110ca9
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DateRequestHandler.java
@@ -0,0 +1,30 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onDateRequested(DateRequestEvent event);
+}
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionEvent.java
new file mode 100644
index 0000000..b5c0a85
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionEvent.java
@@ -0,0 +1,99 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * the type being selected
+ */
+public class DaySelectionEvent extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ /**
+ * Fires a selection event on all registered handlers in the handler
+ * manager.If no such handlers exist, this method will do nothing.
+ *
+ * @param
+ * the selected item type
+ * @param source
+ * the source of the handlers
+ * @param selectedItem
+ * the selected item
+ */
+ public static void fire(HasDaySelectionHandlers source, T selectedItem) {
+ if (TYPE != null) {
+ DaySelectionEvent event = new DaySelectionEvent(
+ selectedItem);
+ source.fireEvent(event);
+ }
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T selectedItem;
+
+ /**
+ * Creates a new selection event.
+ *
+ * @param selectedItem
+ * selected item
+ */
+ protected DaySelectionEvent(T selectedItem) {
+ this.selectedItem = selectedItem;
+ }
+
+ // The instance knows its BeforeSelectionHandler is of type I, but the TYPE
+ // field itself does not, so we have to do an unsafe cast here.
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the selected item.
+ *
+ * @return the selected item
+ */
+ public T getSelectedItem() {
+ return selectedItem;
+ }
+
+ @Override
+ protected void dispatch(DaySelectionHandler handler) {
+ handler.onSelection(this);
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionHandler.java
new file mode 100644
index 0000000..1012197
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionHandler.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface DaySelectionHandler extends EventHandler {
+
+ /**
+ * Called when {@link DaySelectionEvent} is fired.
+ *
+ * @param event the {@link DaySelectionEvent} that was fired
+ */
+ void onSelection(DaySelectionEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DeleteEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/DeleteEvent.java
new file mode 100644
index 0000000..3450fed
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DeleteEvent.java
@@ -0,0 +1,106 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ private boolean cancelled = false;
+
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the target
+ */
+ public static boolean fire(HasDeleteHandlers source, T target) {
+ if (TYPE != null) {
+ DeleteEvent event = new DeleteEvent(target);
+ source.fireEvent(event);
+ return !event.isCancelled();
+ }
+ return true;
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T target;
+
+ /**
+ * Creates a new delete event.
+ *
+ * @param target the ui object being opened
+ */
+ protected DeleteEvent(T target) {
+ this.target = target;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the target.
+ *
+ * @return the target
+ */
+ public T getTarget() {
+ return target;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(DeleteHandler handler) {
+ handler.onDelete(this);
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/DeleteHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/DeleteHandler.java
new file mode 100644
index 0000000..4f86bc2
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/DeleteHandler.java
@@ -0,0 +1,19 @@
+package com.bradrydzewski.gwt.calendar.client.event;
+
+import com.google.gwt.event.shared.EventHandler;
+
+
+/**
+ * Handler interface for {@link DeleteEvent} events.
+ *
+ * @param the type being opened
+ */
+public interface DeleteHandler extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onDelete(DeleteEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasDateRequestHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasDateRequestHandlers.java
new file mode 100644
index 0000000..8f352f7
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasDateRequestHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasDateRequestHandlers extends HasHandlers {
+ /**
+ * Adds a {@link DateRequestEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addDateRequestHandler(DateRequestHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasDaySelectionHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasDaySelectionHandlers.java
new file mode 100644
index 0000000..aa9b63a
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasDaySelectionHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasDaySelectionHandlers extends HasHandlers {
+ /**
+ * Adds a {@link WeekSelectionEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addDaySelectionHandler(DaySelectionHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasDeleteHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasDeleteHandlers.java
new file mode 100644
index 0000000..50eb6ba
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasDeleteHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasDeleteHandlers extends HasHandlers {
+ /**
+ * Adds a {@link DeleteEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addDeleteHandler(DeleteHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasMouseOverHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasMouseOverHandlers.java
new file mode 100644
index 0000000..45b38b8
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasMouseOverHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasMouseOverHandlers extends HasHandlers {
+ /**
+ * Adds a {@link DeleteEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addMouseOverHandler(MouseOverHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasTimeBlockClickHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasTimeBlockClickHandlers.java
new file mode 100644
index 0000000..ac46650
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasTimeBlockClickHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasTimeBlockClickHandlers extends HasHandlers {
+ /**
+ * Adds a {@link DeleteEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addTimeBlockClickHandler(TimeBlockClickHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasUpdateHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasUpdateHandlers.java
new file mode 100644
index 0000000..9e1ba92
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasUpdateHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasUpdateHandlers extends HasHandlers {
+ /**
+ * Adds a {@link UpdateEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addUpdateHandler(UpdateHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/HasWeekSelectionHandlers.java b/src/com/bradrydzewski/gwt/calendar/client/event/HasWeekSelectionHandlers.java
new file mode 100644
index 0000000..ac62d2b
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/HasWeekSelectionHandlers.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface HasWeekSelectionHandlers extends HasHandlers {
+ /**
+ * Adds a {@link WeekSelectionEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addWeekSelectionHandler(WeekSelectionHandler handler);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverEvent.java
new file mode 100644
index 0000000..2ee84da
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverEvent.java
@@ -0,0 +1,91 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ private final T target;
+ private final Object element;
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the dom element
+ */
+ public static void fire(HasMouseOverHandlers source, T target, Object element) {
+ if (TYPE != null) {
+ MouseOverEvent event = new MouseOverEvent(target, element);
+ source.fireEvent(event);
+ }
+ }
+
+ protected MouseOverEvent(T target, Object element) {
+ this.target = target;
+ this.element = element;
+ }
+
+ public T getTarget() {
+ return target;
+ }
+
+ public Object getElement() {
+ return element;
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(MouseOverHandler handler) {
+ handler.onMouseOver(this);
+ }
+
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverHandler.java
new file mode 100644
index 0000000..12303ce
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/MouseOverHandler.java
@@ -0,0 +1,18 @@
+package com.bradrydzewski.gwt.calendar.client.event;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler interface for {@link MouseOverEvent} events.
+ *
+ * @param the type being hovered
+ */
+public interface MouseOverHandler extends EventHandler {
+
+ /**
+ * Called when {@link MouseOverEvent} is fired.
+ *
+ * @param event the {@link MouseOverEvent} that was fired
+ */
+ void onMouseOver(MouseOverEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/RollbackException.java b/src/com/bradrydzewski/gwt/calendar/client/event/RollbackException.java
new file mode 100644
index 0000000..b0514f2
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/RollbackException.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see RollbackException can be thrown to rollback or cancel any
+ * changes made and not yet committed at the time of an Event. An
+ * example is when an {@link com.bradrydzewski.gwt.calendar.client.Appointment}
+ * is deleted by the end-user. A DeleteEvent is raised and the change can be
+ * reversed by throwing the RollbackException.
+ *
+ * @author Brad Rydzewski
+ */
+public class RollbackException extends Exception {
+ /**
+ * Default empty constructor.
+ */
+ public RollbackException() {
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickEvent.java
new file mode 100644
index 0000000..8698651
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickEvent.java
@@ -0,0 +1,96 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the target
+ */
+ public static void fire(HasTimeBlockClickHandlers source, T target) {
+ if (TYPE != null) {
+ TimeBlockClickEvent event = new TimeBlockClickEvent(target);
+ source.fireEvent(event);
+ }
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T target;
+
+ /**
+ * Creates a new delete event.
+ *
+ * @param target the ui object being opened
+ */
+ protected TimeBlockClickEvent(T target) {
+ this.target = target;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the target.
+ *
+ * @return the target
+ */
+ public T getTarget() {
+ return target;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(TimeBlockClickHandler handler) {
+ handler.onTimeBlockClick(this);
+ }
+
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickHandler.java
new file mode 100644
index 0000000..6f4fd00
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickHandler.java
@@ -0,0 +1,18 @@
+package com.bradrydzewski.gwt.calendar.client.event;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler interface for {@link DeleteEvent} events.
+ *
+ * @param the type being opened
+ */
+public interface TimeBlockClickHandler extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onTimeBlockClick(TimeBlockClickEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/UpdateEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/UpdateEvent.java
new file mode 100644
index 0000000..703765f
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/UpdateEvent.java
@@ -0,0 +1,107 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ private boolean cancelled = false;
+
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+
+
+ /**
+ * Fires a open event on all registered handlers in the handler manager.If no
+ * such handlers exist, this method will do nothing.
+ *
+ * @param the target type
+ * @param source the source of the handlers
+ * @param target the target
+ */
+ public static boolean fire(HasUpdateHandlers source, T target) {
+ if (TYPE != null) {
+ UpdateEvent event = new UpdateEvent(target);
+ source.fireEvent(event);
+ return !event.isCancelled();
+ }
+ return true;
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T target;
+
+ /**
+ * Creates a new delete event.
+ *
+ * @param target the ui object being opened
+ */
+ protected UpdateEvent(T target) {
+ this.target = target;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the target.
+ *
+ * @return the target
+ */
+ public T getTarget() {
+ return target;
+ }
+
+ // Because of type erasure, our static type is
+ // wild carded, yet the "real" type should use our I param.
+
+ @Override
+ protected void dispatch(UpdateHandler handler) {
+ handler.onUpdate(this);
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/UpdateHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/UpdateHandler.java
new file mode 100644
index 0000000..f22df4e
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/UpdateHandler.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface UpdateHandler extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onUpdate(UpdateEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionEvent.java b/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionEvent.java
new file mode 100644
index 0000000..0b8ac45
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionEvent.java
@@ -0,0 +1,99 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * the type being selected
+ */
+public class WeekSelectionEvent extends GwtEvent> {
+
+ /**
+ * Handler type.
+ */
+ private static Type> TYPE;
+
+ /**
+ * Fires a selection event on all registered handlers in the handler
+ * manager.If no such handlers exist, this method will do nothing.
+ *
+ * @param
+ * the selected item type
+ * @param source
+ * the source of the handlers
+ * @param selectedItem
+ * the selected item
+ */
+ public static void fire(HasWeekSelectionHandlers source, T selectedItem) {
+ if (TYPE != null) {
+ WeekSelectionEvent event = new WeekSelectionEvent(
+ selectedItem);
+ source.fireEvent(event);
+ }
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type> getType() {
+ if (TYPE == null) {
+ TYPE = new Type>();
+ }
+ return TYPE;
+ }
+
+ private final T selectedItem;
+
+ /**
+ * Creates a new selection event.
+ *
+ * @param selectedItem
+ * selected item
+ */
+ protected WeekSelectionEvent(T selectedItem) {
+ this.selectedItem = selectedItem;
+ }
+
+ // The instance knows its BeforeSelectionHandler is of type I, but the TYPE
+ // field itself does not, so we have to do an unsafe cast here.
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Type> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the selected item.
+ *
+ * @return the selected item
+ */
+ public T getSelectedItem() {
+ return selectedItem;
+ }
+
+ @Override
+ protected void dispatch(WeekSelectionHandler handler) {
+ handler.onSelection(this);
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionHandler.java b/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionHandler.java
new file mode 100644
index 0000000..8e6d324
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionHandler.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see the type being opened
+ */
+public interface WeekSelectionHandler extends EventHandler {
+
+ /**
+ * Called when {@link DeleteEvent} is fired.
+ *
+ * @param event the {@link DeleteEvent} that was fired
+ */
+ void onSelection(WeekSelectionEvent event);
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.java b/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.java
new file mode 100644
index 0000000..d9bd39c
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.java
@@ -0,0 +1,87 @@
+package com.bradrydzewski.gwt.calendar.client.i18n;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * Defines the Strings that can be localized in gwt-cal.
+ *
+ * @author Brad Rydzewski
+ * @author Carlos D. Morales
+ */
+public interface CalendarConstants extends Messages {
+ /**
+ * Footer for days in the MonthView in which there are more
+ * appointments to display than fit in the day cell.
+ *
+ * @param appointments The number of additional appointments in the day
+ * @return The localized string
+ */
+ public String more(int appointments);
+
+ /**
+ * The localized text label for 12 p.m.
+ *
+ * @return The label for the noon
+ */
+ public String noon();
+
+ /**
+ * String representing AM. For certain locales this
+ * will be null.
+ *
+ * @return AM string
+ */
+ public String am();
+
+ /**
+ * String representing AM. For certain locales this
+ * will be null.
+ *
+ * @return PM string
+ */
+ public String pm();
+
+
+ /**
+ * String used to format time in the DayView
+ * components timeline. Example: time is formatted in the US as h AM,
+ * and is formatted in French HH:00.
+ *
+ * @return The localized time format for the DayView
+ */
+ public String timeFormat();
+
+ /**
+ * Each column in the DayView represents a date in time, and
+ * displays the date in its header (i.e. Wed, Jan 15). This property
+ * provides the pattern to format the date.
+ *
+ * @return The localized time format for the DayView
+ */
+ public String dateFormat();
+
+ /**
+ * Each column in the MonthView represents a day of the week,
+ * and displays the day of the week in its header (i.e. Mon, Tue, Wed). This
+ * property provides the pattern to format the day of the week.
+ *
+ * @return The pattern to format the names of the days in the month view
+ */
+ public String weekdayFormat();
+
+ /**
+ * Returns the pattern to format the days in a month.
+ *
+ * @return The pattern to format the names/numbers of days in a month
+ */
+ public String dayOfMonthFormat();
+
+ /**
+ * Returns what the first day of the week is; e.g., SUNDAY in the U.S.,
+ * MONDAY in France.
+ *
+ * @return The text representing the first day of the week, zero-based
+ * (Sunday is 0, Monday is 1, Tuesday is 2, etc.)
+ */
+ public String firstDayOfWeek();
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.properties b/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.properties
new file mode 100644
index 0000000..dae9dba
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.properties
@@ -0,0 +1,9 @@
+noon=Noon
+more=+{0} more
+dayOfMonthFormat=d
+timeFormat=h
+dateFormat=EEE, MMM d
+weekdayFormat=EEE
+am=AM
+pm=PM
+firstDayOfWeek=0
\ No newline at end of file
diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentLayoutDescription.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentLayoutDescription.java
new file mode 100644
index 0000000..2638383
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentLayoutDescription.java
@@ -0,0 +1,105 @@
+package com.bradrydzewski.gwt.calendar.client.monthview;
+
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+
+/**
+ * Contains common properties and behavior for layout descriptions that can be
+ * "stacked" on top of each month view's week.
+ *
+ * @author Carlos D. Morales
+ */
+public class AppointmentLayoutDescription {
+ /**
+ * The layer order (in a stack-like arrangement)assigned to this description
+ * by the AppointmentStackingManager when arranging the
+ * descriptions on the top of a week.
+ */
+ private int stackOrder = 0;
+
+ /**
+ * The start day of the described Appointment when laid on the
+ * top of one of the weeks while visualizing a month through the
+ * MonthView.
+ */
+ private int fromWeekDay = 0;
+
+ /**
+ * The end day of the described Appointment when laid on the top
+ * of one of the weeks while visualizing a month through the
+ * MonthView.
+ */
+ private int toWeekDay = 0;
+
+ /**
+ * The underlying Appointment whose details will be displayed
+ * through the MonthView.
+ */
+ private Appointment appointment = null;
+
+
+ public AppointmentLayoutDescription(int fromWeekDay, int toWeekDay,
+ Appointment appointment) {
+ this.toWeekDay = toWeekDay;
+ this.fromWeekDay = fromWeekDay;
+ this.appointment = appointment;
+ }
+
+ public AppointmentLayoutDescription(int weekDay,
+ Appointment appointment) {
+ this(weekDay, weekDay, appointment);
+ }
+
+ public boolean overlapsWithRange(int from, int to) {
+ return fromWeekDay >= from && fromWeekDay <= to ||
+ fromWeekDay <= from && toWeekDay >= from;
+ }
+
+ public void setStackOrder(int stackOrder) {
+ this.stackOrder = stackOrder;
+ }
+
+ public int getStackOrder() {
+ return stackOrder;
+ }
+
+ public int getWeekStartDay() {
+ return fromWeekDay;
+ }
+
+ public int getWeekEndDay() {
+ return toWeekDay;
+ }
+
+ public boolean spansMoreThanADay() {
+ return fromWeekDay != toWeekDay;
+ }
+
+ public AppointmentLayoutDescription split() {
+ AppointmentLayoutDescription secondPart = null;
+ if (spansMoreThanADay()) {
+ secondPart =
+ new AppointmentLayoutDescription(fromWeekDay + 1, toWeekDay,
+ appointment);
+ this.toWeekDay = this.fromWeekDay;
+ } else {
+ secondPart = this;
+ }
+ return secondPart;
+ }
+
+
+ public Appointment getAppointment() {
+ return appointment;
+ }
+
+ public void setAppointment(Appointment appointment) {
+ this.appointment = appointment;
+ }
+
+ @Override public String toString() {
+ return "AppointmentLayoutDescription{" + "stackOrder=" + stackOrder
+ + ", fromWeekDay=" + fromWeekDay + ", toWeekDay=" + toWeekDay
+ + ", appointment=[" + appointment.getTitle() + "]@"
+ + appointment.hashCode() + '}';
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentStackingManager.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentStackingManager.java
new file mode 100644
index 0000000..0f262cc
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentStackingManager.java
@@ -0,0 +1,258 @@
+package com.bradrydzewski.gwt.calendar.client.monthview;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Manages the AppointmentLayoutDescription in a stack-like
+ * structure arranged in layers. Layers are 0-based (1nd. layer has
+ * an index of 0, 2nd. has an index of 1).
+ *
+ * @author Carlos D. Morales
+ */
+public class AppointmentStackingManager {
+
+ /**
+ * The highest layer index that has been allocated by this manager.
+ */
+ private int highestLayer = 0;
+
+ /**
+ * The collection of AppointmentLayoutDescriptions grouped by by
+ * layer (map key).
+ */
+ private HashMap>
+ layeredDescriptions =
+ new HashMap>();
+
+ private int layerOverflowLimit = Integer.MAX_VALUE;
+
+ public void setLayerOverflowLimit(int layerOverflowLimit) {
+ this.layerOverflowLimit = layerOverflowLimit;
+ }
+
+ /**
+ * Associates the provided description to the first available
+ * layer in the collection administered by this manager. This manager will
+ * look up in the stack of layers (from lowest index to highest index) until
+ * a layer that does not have any AppointmentLayoutDescriptions
+ * that overlap with the days that the description's appointment spans.
+ *
+ * @param description An appointment description object that can be laid on a
+ * layer
+ */
+ public void assignLayer(AppointmentLayoutDescription description) {
+ boolean layerAssigned;
+ int currentlyInspectedLayer = 0;
+ do {
+ initLayer(currentlyInspectedLayer);
+ layerAssigned = assignLayer(currentlyInspectedLayer, description);
+ currentlyInspectedLayer++;
+ } while (!layerAssigned);
+ }
+
+ /**
+ * Returns the lowest layer index that is available on the specified
+ * day.
+ *
+ * @param day The day index for which the lowest layer index will be
+ * attempted to identify
+ * @return An integer representing the index of the layer (zero-based) for
+ * which an single day Appointment can be displayed on.
+ */
+ public int lowestLayerIndex(int day) {
+ return (nextLowestLayerIndex(day, 0));
+ }
+
+ /**
+ * Returns the lowest layer index higher thanfromLayer
+ * that is available on the specified day.
+ *
+ * @param day The day index for which the lowest layer index will be
+ * attempted to identify found
+ * @param fromLayer The layer index after which the search for next
+ * available layer should be started from
+ * @return An integer representing the index of the layer (zero-based) for
+ * which an single day Appointment can be displayed on.
+ */
+ public int nextLowestLayerIndex(int day, int fromLayer) {
+ boolean layerFound = false;
+ int currentlyInspectedLayer = fromLayer;
+ do {
+ if (isLayerAllocated(currentlyInspectedLayer)) {
+ if (overlapsWithDescriptionInLayer(
+ layeredDescriptions.get(currentlyInspectedLayer), day,
+ day)) {
+ currentlyInspectedLayer++;
+ } else {
+ layerFound = true;
+ }
+ } else {
+ layerFound = true;
+ }
+
+ } while (!layerFound);
+ return currentlyInspectedLayer;
+ }
+
+ /**
+ * Returns all the AppointmentLayoutDescriptions in the
+ * specified layer.
+ *
+ * @param layerIndex The index of a layer for which descriptions will be
+ * returned
+ * @return The collection of appointment descriptions in the layer,
+ * null if no appointment has been allocated for the
+ * layer at all
+ */
+ public ArrayList getDescriptionsInLayer(
+ int layerIndex) {
+ return layeredDescriptions.get(layerIndex);
+ }
+
+ /**
+ * Verifies if the range defined by start-end
+ * overlaps with any of the appointment descriptions in
+ * layerDescriptions.
+ *
+ * @param layerDescriptions All the AppointmentLayoutDescription
+ * in a single layer.
+ * @param start The first day of the week in the range to test
+ * @param end The last day of the week in the range to test
+ * @return true if any appointment description in
+ * layerDescriptions overlaps with the specified range,
+ * false otherwise.
+ */
+ private boolean overlapsWithDescriptionInLayer(
+ ArrayList layerDescriptions, int start,
+ int end) {
+ if (layerDescriptions != null) {
+ for (AppointmentLayoutDescription description : layerDescriptions) {
+ if (description.overlapsWithRange(start, end)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean assignLayer(int layer,
+ AppointmentLayoutDescription description) {
+ ArrayList layerDescriptions = layeredDescriptions
+ .get(layer);
+
+ boolean assigned = false;
+ if (!overlapsWithDescriptionInLayer(layerDescriptions,
+ description.getWeekStartDay(), description.getWeekEndDay())) {
+ highestLayer = Math.max(highestLayer, layer);
+ if (layer > layerOverflowLimit && description.spansMoreThanADay()) {
+ AppointmentLayoutDescription split = description.split();
+ layerDescriptions.add(description);
+ assignLayer(split);
+ } else {
+ layerDescriptions.add(description);
+ }
+
+ assigned = true;
+ }
+ return assigned;
+ }
+
+ /**
+ * Indicates whether a specific layerIndex with index layerIndex
+ * has been allocated in the layeredDescriptions map.
+ *
+ * @param layerIndex The index of a layer to verify
+ * @return true if the layeredDescriptions map has
+ * an entry (List<AppointmentLayoutDescription>)
+ * for the layerIndex.
+ */
+ private boolean isLayerAllocated(int layerIndex) {
+ return layeredDescriptions.get(layerIndex) != null;
+ }
+
+ /**
+ * Initializes the collection of descriptions for the layer with the
+ * specified layerIndex.
+ *
+ * @param layerIndex The index of a layer to initialize
+ */
+ private void initLayer(int layerIndex) {
+ if (!isLayerAllocated(layerIndex)) {
+ layeredDescriptions.put(layerIndex,
+ new ArrayList());
+ }
+ }
+
+ /**
+ * Returns the number of appointments (multi-day or all-day, as that's the
+ * type of appointment that the stacking manager deals with only) that
+ * exceeded the layerOverflowLimit value when they were
+ * stacked.
+ *
+ * @param day The day to perform the count
+ * @return The number of days that got a layer higher than the configured
+ * layerOverflowLimit layer, if there were any
+ */
+ public int multidayAppointmentsOverLimitOn(int day) {
+ int count = 0;
+ for (int layer = 0; layer <= highestLayer; layer++) {
+ ArrayList descriptions =
+ layeredDescriptions.get(layer);
+ if (descriptions != null) {
+ for (AppointmentLayoutDescription description : descriptions) {
+ if (layer > layerOverflowLimit &&
+ description.overlapsWithRange(day, day)) {
+ count++;
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Indicates whether there are any appointments that encompass the
+ * specified day.
+ *
+ * @param day The day to test for appointments
+ * @return true if there are any descriptions in any layer for
+ * the specified day.
+ */
+ public boolean areThereAppointmentsOn(int day) {
+ boolean thereAre = false;
+ for (int layersIndex = 0; layersIndex <= highestLayer; layersIndex++) {
+ ArrayList layerDescriptions =
+ layeredDescriptions.get(layersIndex);
+ if (overlapsWithDescriptionInLayer(layerDescriptions, day, day)) {
+ thereAre = true;
+ break;
+ }
+ }
+ return thereAre;
+ }
+
+ @Override public String toString() {
+ StringBuilder managerState = new StringBuilder();
+ for (int i = 0; i <= highestLayer; i++) {
+ ArrayList descriptions =
+ this.getDescriptionsInLayer(i);
+ if (descriptions == null) continue;
+ for (AppointmentLayoutDescription desc : descriptions) {
+ managerState.append("[").append(i).append("]");
+ for (int before = 0; before < desc.getWeekStartDay(); before++) {
+ managerState.append("_");
+ }
+ for (int dur = desc.getWeekStartDay(); dur <= desc.getWeekEndDay();
+ dur++) {
+ managerState.append("X");
+ }
+ for (int after = desc.getWeekEndDay(); after < 6; after++) {
+ managerState.append("_");
+ }
+ managerState.append(" ->").append(desc).append("\n");
+ }
+ }
+ return managerState.toString();
+ }
+}
diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidget.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidget.java
new file mode 100644
index 0000000..4d610c9
--- /dev/null
+++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidget.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of gwt-cal
+ * Copyright (C) 2010 Scottsdale Software LLC
+ *
+ * gwt-cal is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see Appointment in a
+ * MonthView.