From a0da681497aa7e2cc0d71fa9aebc7dba1c465385 Mon Sep 17 00:00:00 2001 From: Dobie Wollert Date: Tue, 4 Dec 2012 03:57:45 -0800 Subject: [PATCH] Fixed 'Tetris' calendar problem. --- .classpath | 16 +- .../com.google.appengine.eclipse.core.prefs | 2 + .settings/com.google.gwt.eclipse.core.prefs | 1 + server.properties | 7 + .../client/drop/DayViewDropController.java | 181 +++++ .../drop/DayViewPickupDragController.java | 55 ++ .../client/drop/DayViewResizeController.java | 85 ++ .../client/drop/MonthViewDropController.java | 227 ++++++ .../drop/MonthViewPickupDragController.java | 19 + src/com/biomed/Biomed.gwt.xml | 3 + .../resources/BiomedTechViewStyleManager.java | 18 + .../client/ui/schedule/SchedulePanel.java | 33 +- .../client/ui/schedule/SchedulePanel.ui.xml | 16 +- src/com/biomed/server/BiomedModule.java | 3 +- src/com/biomed/server/CalendarApi.java | 217 ++--- .../gwt/calendar/Calendar.gwt.xml | 49 ++ .../gwt/calendar/client/Appointment.java | 455 +++++++++++ .../calendar/client/AppointmentManager.java | 355 ++++++++ .../gwt/calendar/client/AppointmentStyle.java | 12 + .../gwt/calendar/client/Attendee.java | 150 ++++ .../gwt/calendar/client/Attending.java | 46 ++ .../gwt/calendar/client/Calendar.java | 163 ++++ .../gwt/calendar/client/CalendarFormat.java | 312 +++++++ .../gwt/calendar/client/CalendarSettings.java | 182 +++++ .../gwt/calendar/client/CalendarView.java | 285 +++++++ .../gwt/calendar/client/CalendarViews.java | 48 ++ .../gwt/calendar/client/CalendarWidget.java | 565 +++++++++++++ .../gwt/calendar/client/DateUtils.java | 405 ++++++++++ .../gwt/calendar/client/HasAppointments.java | 17 + .../gwt/calendar/client/HasLayout.java | 21 + .../gwt/calendar/client/HasSettings.java | 7 + .../calendar/client/InteractiveWidget.java | 253 ++++++ .../client/ThemeAppointmentStyle.java | 46 ++ .../calendar/client/agenda/AgendaView.java | 397 +++++++++ .../client/dayview/AppointmentAdapter.java | 170 ++++ .../client/dayview/AppointmentWidget.java | 241 ++++++ .../gwt/calendar/client/dayview/DayView.java | 571 +++++++++++++ .../calendar/client/dayview/DayViewBody.java | 100 +++ .../calendar/client/dayview/DayViewGrid.java | 105 +++ .../client/dayview/DayViewHeader.java | 220 +++++ .../client/dayview/DayViewLayoutStrategy.java | 439 ++++++++++ .../client/dayview/DayViewMultiDayBody.java | 108 +++ .../client/dayview/DayViewStyleManager.java | 80 ++ .../client/dayview/DayViewTimeline.java | 108 +++ .../calendar/client/dayview/TimeBlock.java | 148 ++++ .../calendar/client/event/CreateEvent.java | 107 +++ .../calendar/client/event/CreateHandler.java | 36 + .../client/event/DateRequestEvent.java | 104 +++ .../client/event/DateRequestHandler.java | 30 + .../client/event/DaySelectionEvent.java | 99 +++ .../client/event/DaySelectionHandler.java | 36 + .../calendar/client/event/DeleteEvent.java | 106 +++ .../calendar/client/event/DeleteHandler.java | 19 + .../client/event/HasDateRequestHandlers.java | 37 + .../client/event/HasDaySelectionHandlers.java | 37 + .../client/event/HasDeleteHandlers.java | 37 + .../client/event/HasMouseOverHandlers.java | 37 + .../event/HasTimeBlockClickHandlers.java | 37 + .../client/event/HasUpdateHandlers.java | 37 + .../event/HasWeekSelectionHandlers.java | 37 + .../calendar/client/event/MouseOverEvent.java | 91 +++ .../client/event/MouseOverHandler.java | 18 + .../client/event/RollbackException.java | 35 + .../client/event/TimeBlockClickEvent.java | 96 +++ .../client/event/TimeBlockClickHandler.java | 18 + .../calendar/client/event/UpdateEvent.java | 107 +++ .../calendar/client/event/UpdateHandler.java | 36 + .../client/event/WeekSelectionEvent.java | 99 +++ .../client/event/WeekSelectionHandler.java | 36 + .../client/i18n/CalendarConstants.java | 87 ++ .../client/i18n/CalendarConstants.properties | 9 + .../AppointmentLayoutDescription.java | 105 +++ .../monthview/AppointmentStackingManager.java | 258 ++++++ .../client/monthview/AppointmentWidget.java | 63 ++ .../monthview/AppointmentWidgetParts.java | 12 + .../monthview/DayLayoutDescription.java | 53 ++ .../monthview/MonthLayoutDescription.java | 138 ++++ .../calendar/client/monthview/MonthView.java | 764 ++++++++++++++++++ .../client/monthview/MonthViewDateUtils.java | 118 +++ .../monthview/MonthViewStyleManager.java | 123 +++ .../monthview/WeekLayoutDescription.java | 143 ++++ .../client/techview/AppointmentAdapter.java | 170 ++++ .../client/techview/AppointmentWidget.java | 241 ++++++ .../calendar/client/techview/DayViewBody.java | 100 +++ .../calendar/client/techview/DayViewGrid.java | 105 +++ .../client/techview/DayViewHeader.java | 225 ++++++ .../client/techview/DayViewMultiDayBody.java | 108 +++ .../client/techview/DayViewTimeline.java | 108 +++ .../calendar/client/techview/TechView.java | 571 +++++++++++++ .../techview/TechViewLayoutStrategy.java | 440 ++++++++++ .../client/techview/TechViewStyleManager.java | 80 ++ .../calendar/client/techview/TimeBlock.java | 148 ++++ .../calendar/client/util/AppointmentUtil.java | 114 +++ .../calendar/client/util/FormattingUtil.java | 30 + .../gwt/calendar/client/util/WindowUtils.java | 85 ++ .../client/util/impl/FormattingIE6Impl.java | 9 + .../client/util/impl/FormattingImpl.java | 8 + .../gwt/calendar/theme/google/Google.gwt.xml | 15 + .../google/client/GoogleAppointmentStyle.java | 177 ++++ .../google/client/GoogleAppointmentTheme.java | 60 ++ .../client/GoogleDayViewStyleManager.java | 43 + .../client/GoogleMonthViewStyleManager.java | 42 + .../client/GoogleTechViewStyleManager.java | 43 + .../theme/google/public/gwt-cal-google.css | 419 ++++++++++ .../ical/client/ICalAppointmentStyle.java | 166 ++++ .../ical/client/ICalAppointmentTheme.java | 32 + .../ical/client/ICalDayViewStyleManager.java | 43 + .../client/ICalMonthViewStyleManager.java | 36 + .../gwt/calendar/theme/ical/iCal.gwt.xml | 10 + .../theme/ical/public/blue-appt-gradient.gif | Bin 0 -> 646 bytes .../ical/public/fuschia-appt-gradient.gif | Bin 0 -> 416 bytes .../theme/ical/public/green-appt-gradient.gif | Bin 0 -> 644 bytes .../theme/ical/public/gwt-cal-apple.css | 359 ++++++++ .../ical/public/orange-appt-gradient.gif | Bin 0 -> 646 bytes .../ical/public/purple-appt-gradient.gif | Bin 0 -> 646 bytes .../theme/ical/public/red-appt-gradient.gif | Bin 0 -> 434 bytes .../calendar/theme/outlook/Outlook.gwt.xml | 3 + .../outlook/public/diagonol-line-pattern.gif | Bin 0 -> 54 bytes .../theme/outlook/public/gwt-cal-outlook.css | 380 +++++++++ war/WEB-INF/lib/gwt-cal-0.9.3.jar | Bin 267186 -> 0 bytes war/WEB-INF/lib/gwt-servlet.jar | Bin 6175753 -> 6212463 bytes war/resources/css/styles.css | 27 + 122 files changed, 14427 insertions(+), 116 deletions(-) create mode 100644 .settings/com.google.appengine.eclipse.core.prefs create mode 100644 server.properties create mode 100644 src/com/allen_sauer/gwt/dnd/client/drop/DayViewDropController.java create mode 100644 src/com/allen_sauer/gwt/dnd/client/drop/DayViewPickupDragController.java create mode 100644 src/com/allen_sauer/gwt/dnd/client/drop/DayViewResizeController.java create mode 100644 src/com/allen_sauer/gwt/dnd/client/drop/MonthViewDropController.java create mode 100644 src/com/allen_sauer/gwt/dnd/client/drop/MonthViewPickupDragController.java create mode 100644 src/com/biomed/client/resources/BiomedTechViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/Calendar.gwt.xml create mode 100644 src/com/bradrydzewski/gwt/calendar/client/Appointment.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/AppointmentManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/AppointmentStyle.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/Attendee.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/Attending.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/Calendar.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/CalendarFormat.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/CalendarSettings.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/CalendarView.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/CalendarViews.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/CalendarWidget.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/DateUtils.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/HasAppointments.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/HasLayout.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/HasSettings.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/InteractiveWidget.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/ThemeAppointmentStyle.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/agenda/AgendaView.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentAdapter.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/AppointmentWidget.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayView.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewBody.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewGrid.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewHeader.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewLayoutStrategy.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewMultiDayBody.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/DayViewTimeline.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/dayview/TimeBlock.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/CreateEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/CreateHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DateRequestEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DateRequestHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DaySelectionHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DeleteEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/DeleteHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasDateRequestHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasDaySelectionHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasDeleteHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasMouseOverHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasTimeBlockClickHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasUpdateHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/HasWeekSelectionHandlers.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/MouseOverEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/MouseOverHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/RollbackException.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/TimeBlockClickHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/UpdateEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/UpdateHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionEvent.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/event/WeekSelectionHandler.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/i18n/CalendarConstants.properties create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentLayoutDescription.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentStackingManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidget.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidgetParts.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/DayLayoutDescription.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/MonthLayoutDescription.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/MonthView.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/MonthViewDateUtils.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/MonthViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/monthview/WeekLayoutDescription.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/AppointmentAdapter.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/AppointmentWidget.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/DayViewBody.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/DayViewGrid.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/DayViewHeader.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/DayViewMultiDayBody.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/DayViewTimeline.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/TechView.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/TechViewLayoutStrategy.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/TechViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/techview/TimeBlock.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/util/AppointmentUtil.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/util/FormattingUtil.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/util/WindowUtils.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/util/impl/FormattingIE6Impl.java create mode 100644 src/com/bradrydzewski/gwt/calendar/client/util/impl/FormattingImpl.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/Google.gwt.xml create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/client/GoogleAppointmentStyle.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/client/GoogleAppointmentTheme.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/client/GoogleDayViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/client/GoogleMonthViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/client/GoogleTechViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/google/public/gwt-cal-google.css create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/client/ICalAppointmentStyle.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/client/ICalAppointmentTheme.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/client/ICalDayViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/client/ICalMonthViewStyleManager.java create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/iCal.gwt.xml create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/blue-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/fuschia-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/green-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/gwt-cal-apple.css create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/orange-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/purple-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/ical/public/red-appt-gradient.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/outlook/Outlook.gwt.xml create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/outlook/public/diagonol-line-pattern.gif create mode 100644 src/com/bradrydzewski/gwt/calendar/theme/outlook/public/gwt-cal-outlook.css delete mode 100644 war/WEB-INF/lib/gwt-cal-0.9.3.jar 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"> + + 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. + *

+ * + * @author Brad Rydzewski + * @author Carlos D. Morales + */ +@SuppressWarnings("serial") +public class Appointment implements Comparable, Serializable { + + private String id; + private String title; + private String description; + private Date start; + private Date end; + private String location; + private String createdBy; + private List attendees = new ArrayList(); + private boolean allDay = false; + private AppointmentStyle style = AppointmentStyle.DEFAULT; + private String customStyle; + private boolean readOnly = false; + private int columnId; + + public int getColumnId() { + return columnId; + } + + public void setColumnId(int columnId) { + this.columnId = columnId; + } + + /** + *

+ * 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 before appointment, 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 than fromLayer + * 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.

Through an association to a domain-model + * Appointment, AppointmentWidgets allow displaying + * the appointment details and to capture mouse and keyboard events. + *

+ * + * @author Brad Rydzewski + * @author Carlos D. Morales + */ +public class AppointmentWidget extends FocusPanel { + /** + * The underlying Appointment represented by this panel. + */ + private Appointment appointment; + + /** + * Creates an AppointmentWidget with a reference to the provided + * appointment. + * + * @param appointment The appointment to be displayed through this panel + * widget + */ + public AppointmentWidget(Appointment appointment) { + this.appointment = appointment; + Label titleLabel = new Label(); + titleLabel.getElement().setInnerHTML(this.appointment.getTitle()); + this.add(titleLabel); + } + + /** + * Returns the Appointment this panel represents/is associated + * to. + * + * @return The domain model appointment rendered through this panel + */ + public Appointment getAppointment() { + return appointment; + } +} diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidgetParts.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidgetParts.java new file mode 100644 index 0000000..6adfb1a --- /dev/null +++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/AppointmentWidgetParts.java @@ -0,0 +1,12 @@ +package com.bradrydzewski.gwt.calendar.client.monthview; + +/** + * Indicates whether the presence of an Appointment that spans + * multiple weeks in a month view is the first, second or before the + * last, or the last. + * + * @author Carlos D. Morales + */ +public enum AppointmentWidgetParts { + FIRST_WEEK, IN_BETWEEN, LAST_WEEK +} diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/DayLayoutDescription.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/DayLayoutDescription.java new file mode 100644 index 0000000..9169d5e --- /dev/null +++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/DayLayoutDescription.java @@ -0,0 +1,53 @@ +package com.bradrydzewski.gwt.calendar.client.monthview; + +import java.util.ArrayList; +import java.util.List; + +import com.bradrydzewski.gwt.calendar.client.Appointment; + +/** + * Contains the calculated layout description of all Appointments + * in single day part of a week row in a MonthView. + *

Note: A DayLayoutDescription is not + * aware of multi-day Appointments that might span the day + * represented by this description. + * + * @author Carlos D. Morales + */ +public class DayLayoutDescription { + /** + * The list of simple appointments (not multiday, not all-day) in + * this single day. + */ + private List appointments = new ArrayList(); + + /** + * The index of the represented day in the corresponding parent week. + */ + private int dayIndex = -1; + + public DayLayoutDescription(int dayIndex) { + this.dayIndex = dayIndex; + } + + public List getAppointments() { + return appointments; + } + + public int getTotalAppointmentCount() { + return appointments.size(); + } + + public void addAppointment(Appointment appointment) { + if (!appointment.isMultiDay()) { + appointments.add(appointment); + } else { + throw new IllegalArgumentException( + "Attempted to add a multiday appointment to a single day description"); + } + } + + public int getDayIndex() { + return dayIndex; + } +} \ No newline at end of file diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthLayoutDescription.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthLayoutDescription.java new file mode 100644 index 0000000..3d85a14 --- /dev/null +++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthLayoutDescription.java @@ -0,0 +1,138 @@ +package com.bradrydzewski.gwt.calendar.client.monthview; + +import java.util.Date; +import java.util.List; + +import com.bradrydzewski.gwt.calendar.client.Appointment; +import com.bradrydzewski.gwt.calendar.client.DateUtils; + +/** + * Describes the layout for all appointments in all the weeks displayed in a + * MonthView. This class is responsible for the distribution of the + * appointments over the multiple weeks they possibly span. + * + * @author Carlos D. Morales + */ +public class MonthLayoutDescription { + + private Date calendarFirstDay = null; + + private Date calendarLastDay = null; + + private WeekLayoutDescription[] weeks = new WeekLayoutDescription[6]; + + public MonthLayoutDescription(Date calendarFirstDay, + int monthViewRequiredRows, + List appointments, int maxLayer) { + this.calendarFirstDay = calendarFirstDay; + this.calendarLastDay = + calculateLastDate(calendarFirstDay, monthViewRequiredRows); + placeAppointments(appointments, maxLayer); + } + + public MonthLayoutDescription(Date calendarFirstDay, + int monthViewRequiredRows, + List appointments) { + this(calendarFirstDay, monthViewRequiredRows, + appointments, Integer.MAX_VALUE); + } + + private void initWeek(int weekIndex, int maxLayer) { + if (weeks[weekIndex] == null) { + weeks[weekIndex] = new WeekLayoutDescription(calendarFirstDay, + calendarLastDay, + maxLayer); + } + } + + private void placeAppointments(List appointments, + int maxLayer) { + + for (Appointment appointment : appointments) { + if (overlapsWithMonth(appointment, calendarFirstDay, + calendarLastDay)) { + int startWeek = + calculateWeekFor(appointment.getStart(), calendarFirstDay); + + /* Place appointments only in this month */ + if (startWeek >= 0 && startWeek < weeks.length) { + initWeek(startWeek, maxLayer); + if (appointment.isMultiDay() || appointment.isAllDay()) { + positionMultidayAppointment(startWeek, appointment, maxLayer); + } else { + weeks[startWeek].addAppointment(appointment); + } + } + } + } + } + + private boolean isMultiWeekAppointment(int startWeek, int endWeek) { + return startWeek != endWeek; + } + + private void positionMultidayAppointment(int startWeek, + Appointment appointment, int maxLayer) { + int endWeek = calculateWeekFor(appointment.getEnd(), calendarFirstDay); + + initWeek(endWeek, maxLayer); + if (isMultiWeekAppointment(startWeek, endWeek)) { + distributeOverWeeks(startWeek, endWeek, appointment, maxLayer); + } else { + weeks[startWeek].addMultiDayAppointment(appointment); + } + } + + private void distributeOverWeeks(int startWeek, int endWeek, + Appointment appointment, int maxLayer) { + weeks[startWeek].addMultiWeekAppointment(appointment, + AppointmentWidgetParts.FIRST_WEEK); + for (int week = startWeek + 1; week < endWeek; week++) { + initWeek(week, maxLayer); + weeks[week].addMultiWeekAppointment(appointment, + AppointmentWidgetParts.IN_BETWEEN); + } + if (startWeek < endWeek) { + initWeek(endWeek, maxLayer); + weeks[endWeek].addMultiWeekAppointment(appointment, + AppointmentWidgetParts.LAST_WEEK); + } + } + + private boolean overlapsWithMonth(Appointment appointment, + Date calendarFirstDate, Date calendarLastDate) { + return !(appointment.getStart().before(calendarFirstDate) + && appointment.getEnd().before(calendarFirstDate) + || appointment.getStart().after(calendarLastDate) + && appointment.getEnd().after(calendarLastDate)); + } + + private int calculateWeekFor(Date testDate, Date calendarFirstDate) { + //fix for issue 66. differenceInDays returns abs value, causing problems + if(testDate.before(calendarFirstDate)) + return 0; + + int week = (int) Math.floor( + DateUtils.differenceInDays( + testDate, calendarFirstDate) / 7d); + + return Math.min(week, weeks.length - 1); + } + + @SuppressWarnings("deprecation") + private Date calculateLastDate(final Date startDate, int weeks) { + int daysInMonthGrid = weeks * 7; + Date endDate = (Date) startDate.clone(); + endDate.setDate(endDate.getDate() + daysInMonthGrid - 1); + // fix for issue 164: The endDate is at the end of the day + endDate.setHours(23); + endDate.setMinutes(59); + endDate.setSeconds(59); + return endDate; + } + + public WeekLayoutDescription[] getWeekDescriptions() { + return weeks; + } + +} diff --git a/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthView.java b/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthView.java new file mode 100644 index 0000000..58cb9d9 --- /dev/null +++ b/src/com/bradrydzewski/gwt/calendar/client/monthview/MonthView.java @@ -0,0 +1,764 @@ +/* + * 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 + * A CalendarView that displays appointments for a given month. The Month is + * displayed in a grid-style view where cells represents days, columns + * represents days of the week (i.e. Monday, Tuesday, etc.) and rows represent a + * full week (Sunday through Saturday). + *

+ *

+ *

CSS Style Rules

    + *
  • .gwt-cal-MonthView { }
  • + *
  • .dayCell { cell that represents a day }
  • + *
  • .dayCell-today { cell that represents today }
  • + *
  • .dayCell-disabled { cell's day falls outside the month }
  • + *
  • .dayCell-today-disabled { cell represents today, falls outside the month + * }
  • + *
  • .dayCellLabel { header for the cell }
  • + *
  • .dayCellLabel-today { cell represents today }
  • + *
  • .dayCellLabel-disabled { cell's day falls outside the month }
  • + *
  • .dayCellLabel-today-disabled { cell represents today, falls outside the + * month }
  • + *
  • .weekDayLabel { label for the days of the week }
+ * + * @author Brad Rydzewski + * @since 0.9.0 + */ +public class MonthView extends CalendarView implements HasWeekSelectionHandlers, HasDaySelectionHandlers { + + public static final Comparator APPOINTMENT_COMPARATOR = new Comparator() { + + public int compare(Appointment a1, Appointment a2) { + int compare = Boolean.valueOf(a2.isMultiDay()).compareTo( + a1.isMultiDay()); + + if (compare == 0) { + compare = a1.getStart().compareTo(a2.getStart()); + } + + if (compare == 0) { + compare = a2.getEnd().compareTo(a1.getEnd()); + } + + return compare; + } + }; + + private static final int DAYS_IN_A_WEEK = 7; + private final static String MONTH_VIEW = "gwt-cal-MonthView"; + private final static String CANVAS_STYLE = "canvas"; + private final static String GRID_STYLE = "grid"; + private final static String CELL_STYLE = "dayCell"; + private final static String MORE_LABEL_STYLE = "moreAppointments"; + private final static String CELL_HEADER_STYLE = "dayCellLabel"; + private final static String WEEKDAY_LABEL_STYLE = "weekDayLabel"; + private final static String WEEKNUMBER_LABEL_STYLE = "weekNumberLabel"; + + + /** + * List of appointment panels drawn on the month view canvas. + */ + private ArrayList appointmentsWidgets = new ArrayList(); + + /** + * All appointments are placed on this canvas and arranged. + */ + private AbsolutePanel appointmentCanvas = new AbsolutePanel(); + + /** + * All "+ n more" Labels, mapped to its cell in the MonthView Grid. + */ + private HashMap moreLabels = new HashMap(); + private ArrayList