summaryrefslogtreecommitdiffstats
path: root/client/src
diff options
context:
space:
mode:
authorJohn Ahlroos <john@vaadin.com>2013-03-27 16:33:28 +0200
committerVaadin Code Review <review@vaadin.com>2013-04-03 08:03:37 +0000
commit217ba18e53a8607a9e2480574ec1c3da11f4037f (patch)
tree2c38b306985b77144e0797e8bc83d386c8575aa3 /client/src
parent1d25d6d6427f94e93e3bf7417aa968aaa9673dab (diff)
downloadvaadin-framework-217ba18e53a8607a9e2480574ec1c3da11f4037f.tar.gz
vaadin-framework-217ba18e53a8607a9e2480574ec1c3da11f4037f.zip
Integrate Calendar into core #11079
Everything else integrated, except TB3 tests (ticket #11090, old TB2 tests used instead) Change-Id: If1700d7680a6c0a45f84d6e3c7b80e6536da78c8
Diffstat (limited to 'client/src')
-rw-r--r--client/src/com/vaadin/client/ui/VCalendar.java1444
-rw-r--r--client/src/com/vaadin/client/ui/calendar/CalendarConnector.java637
-rw-r--r--client/src/com/vaadin/client/ui/calendar/VCalendarAction.java138
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java55
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java313
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java808
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java114
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java659
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java60
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java70
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java179
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java117
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java129
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java119
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java33
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java142
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java215
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java696
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java97
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java108
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java677
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java61
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java51
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java184
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java67
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java64
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java166
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java181
28 files changed, 7584 insertions, 0 deletions
diff --git a/client/src/com/vaadin/client/ui/VCalendar.java b/client/src/com/vaadin/client/ui/VCalendar.java
new file mode 100644
index 0000000000..e66a2d7552
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/VCalendar.java
@@ -0,0 +1,1444 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.calendar.schedule.CalendarDay;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.client.ui.calendar.schedule.DayToolbar;
+import com.vaadin.client.ui.calendar.schedule.MonthGrid;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayToolbar;
+import com.vaadin.client.ui.calendar.schedule.SimpleWeekToolbar;
+import com.vaadin.client.ui.calendar.schedule.WeekGrid;
+import com.vaadin.client.ui.calendar.schedule.WeeklyLongEvents;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Client side implementation for Calendar
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class VCalendar extends Composite {
+
+ public static final String ATTR_FIRSTDAYOFWEEK = "firstDay";
+ public static final String ATTR_LASTDAYOFWEEK = "lastDay";
+ public static final String ATTR_FIRSTHOUROFDAY = "firstHour";
+ public static final String ATTR_LASTHOUROFDAY = "lastHour";
+
+ // private boolean hideWeekends;
+ private String[] monthNames;
+ private String[] dayNames;
+ private boolean format;
+ private final DockPanel outer = new DockPanel();
+ private int rows;
+
+ private boolean rangeSelectAllowed = true;
+ private boolean rangeMoveAllowed = true;
+ private boolean eventResizeAllowed = true;
+ private boolean eventMoveAllowed = true;
+
+ private final SimpleDayToolbar nameToolbar = new SimpleDayToolbar();
+
+ private final DayToolbar dayToolbar = new DayToolbar(this);
+ private final SimpleWeekToolbar weekToolbar;
+ private WeeklyLongEvents weeklyLongEvents;
+ private MonthGrid monthGrid;
+ private WeekGrid weekGrid;
+ private int intWidth = 0;
+ private int intHeight = 0;
+
+ protected final DateTimeFormat dateformat_datetime = DateTimeFormat
+ .getFormat("yyyy-MM-dd HH:mm:ss");
+ protected final DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat("yyyy-MM-dd");
+ protected final DateTimeFormat time12format_date = DateTimeFormat
+ .getFormat("h:mm a");
+ protected final DateTimeFormat time24format_date = DateTimeFormat
+ .getFormat("HH:mm");
+
+ private boolean readOnly = false;
+ private boolean disabled = false;
+
+ private boolean isHeightUndefined = false;
+
+ private boolean isWidthUndefined = false;
+ private int firstDay;
+ private int lastDay;
+ private int firstHour;
+ private int lastHour;
+
+ /**
+ * Listener interface for listening to event click events
+ */
+ public interface DateClickListener {
+ /**
+ * Triggered when a date was clicked
+ *
+ * @param date
+ * The date and time that was clicked
+ */
+ void dateClick(String date);
+ }
+
+ /**
+ * Listener interface for listening to week number click events
+ */
+ public interface WeekClickListener {
+ /**
+ * Called when a week number was selected.
+ *
+ * @param event
+ * The format of the vent string is "<year>w<week>"
+ */
+ void weekClick(String event);
+ }
+
+ /**
+ * Listener interface for listening to forward events
+ */
+ public interface ForwardListener {
+
+ /**
+ * Called when the calendar should move one view forward
+ */
+ void forward();
+ }
+
+ /**
+ * Listener interface for listening to backward events
+ */
+ public interface BackwardListener {
+
+ /**
+ * Called when the calendar should move one view backward
+ */
+ void backward();
+ }
+
+ /**
+ * Listener interface for listening to selection events
+ */
+ public interface RangeSelectListener {
+
+ /**
+ * Called when a user selected a new event by highlighting an area of
+ * the calendar.
+ *
+ * FIXME Fix the value nonsense.
+ *
+ * @param value
+ * The format of the value string is
+ * "<year>:<start-minutes>:<end-minutes>" if called from the
+ * {@link SimpleWeekToolbar} and "<yyyy-MM-dd>TO<yyyy-MM-dd>"
+ * if called from {@link MonthGrid}
+ */
+ void rangeSelected(String value);
+ }
+
+ /**
+ * Listener interface for listening to click events
+ */
+ public interface EventClickListener {
+ /**
+ * Called when an event was clicked
+ *
+ * @param event
+ * The event that was clicked
+ */
+ void eventClick(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for listening to event moved events. Occurs when a
+ * user drags an event to a new position
+ */
+ public interface EventMovedListener {
+ /**
+ * Triggered when an event was dragged to a new position and the start
+ * and end dates was changed
+ *
+ * @param event
+ * The event that was moved
+ */
+ void eventMoved(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for when an event gets resized (its start or end date
+ * changes)
+ */
+ public interface EventResizeListener {
+ /**
+ * Triggers when the time limits for the event was changed.
+ *
+ * @param event
+ * The event that was changed. The new time limits have been
+ * updated in the event before calling this method
+ */
+ void eventResized(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for listening to scroll events.
+ */
+ public interface ScrollListener {
+ /**
+ * Triggered when the calendar is scrolled
+ *
+ * @param scrollPosition
+ * The scroll position in pixels as returned by
+ * {@link ScrollPanel#getScrollPosition()}
+ */
+ void scroll(int scrollPosition);
+ }
+
+ /**
+ * Listener interface for listening to mouse events.
+ */
+ public interface MouseEventListener {
+ /**
+ * Triggered when a user wants an context menu
+ *
+ * @param event
+ * The context menu event
+ *
+ * @param widget
+ * The widget that the context menu should be added to
+ */
+ void contextMenu(ContextMenuEvent event, Widget widget);
+ }
+
+ /**
+ * Default constructor
+ */
+ public VCalendar() {
+ weekToolbar = new SimpleWeekToolbar(this);
+ initWidget(outer);
+ setStylePrimaryName("v-calendar");
+ blockSelect(getElement());
+ }
+
+ /**
+ * Hack for IE to not select text when dragging.
+ *
+ * @param e
+ * The element to apply the hack on
+ */
+ private native void blockSelect(Element e)
+ /*-{
+ e.onselectstart = function() {
+ return false;
+ }
+
+ e.ondragstart = function() {
+ return false;
+ }
+ }-*/;
+
+ private void updateEventsToWeekGrid(CalendarEvent[] events) {
+ List<CalendarEvent> allDayLong = new ArrayList<CalendarEvent>();
+ List<CalendarEvent> belowDayLong = new ArrayList<CalendarEvent>();
+
+ for (CalendarEvent e : events) {
+ if (e.isAllDay()) {
+ // Event is set on one "allDay" event or more than one.
+ allDayLong.add(e);
+
+ } else {
+ // Event is set only on one day.
+ belowDayLong.add(e);
+ }
+ }
+
+ weeklyLongEvents.addEvents(allDayLong);
+
+ for (CalendarEvent e : belowDayLong) {
+ weekGrid.addEvent(e);
+ }
+ }
+
+ /**
+ * Adds events to the month grid
+ *
+ * @param events
+ * The events to add
+ * @param drawImmediately
+ * Should the grid be rendered immediately. (currently not in
+ * use)
+ *
+ */
+ public void updateEventsToMonthGrid(Collection<CalendarEvent> events,
+ boolean drawImmediately) {
+ for (CalendarEvent e : sortEventsByDuration(events)) {
+ // FIXME Why is drawImmediately not used ?????
+ addEventToMonthGrid(e, false);
+ }
+ }
+
+ private void addEventToMonthGrid(CalendarEvent e, boolean renderImmediately) {
+ Date when = e.getStart();
+ Date to = e.getEnd();
+ boolean eventAdded = false;
+ boolean inProgress = false; // Event adding has started
+ boolean eventMoving = false;
+ List<SimpleDayCell> dayCells = new ArrayList<SimpleDayCell>();
+ List<SimpleDayCell> timeCells = new ArrayList<SimpleDayCell>();
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ if (eventAdded) {
+ break;
+ }
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(row,
+ cell);
+ if (isEventInDay(when, to, sdc.getDate())
+ && isEventInDayWithTime(when, to, sdc.getDate(),
+ e.getEndTime(), e.isAllDay())) {
+ if (!eventMoving) {
+ eventMoving = sdc.getMoveEvent() != null;
+ }
+ long d = e.getRangeInMilliseconds();
+ if ((d > 0 && d <= DateConstants.DAYINMILLIS)
+ && !e.isAllDay()) {
+ timeCells.add(sdc);
+ } else {
+ dayCells.add(sdc);
+ }
+ inProgress = true;
+ continue;
+ } else if (inProgress) {
+ eventAdded = true;
+ inProgress = false;
+ break;
+ }
+ }
+ }
+
+ updateEventSlotIndex(e, dayCells);
+ updateEventSlotIndex(e, timeCells);
+
+ for (SimpleDayCell sdc : dayCells) {
+ sdc.addCalendarEvent(e);
+ }
+ for (SimpleDayCell sdc : timeCells) {
+ sdc.addCalendarEvent(e);
+ }
+
+ if (renderImmediately) {
+ reDrawAllMonthEvents(!eventMoving);
+ }
+ }
+
+ /*
+ * We must also handle the special case when the event lasts exactly for 24
+ * hours, thus spanning two days e.g. from 1.1.2001 00:00 to 2.1.2001 00:00.
+ * That special case still should span one day when rendered.
+ */
+ @SuppressWarnings("deprecation")
+ // Date methods are not deprecated in GWT
+ private boolean isEventInDayWithTime(Date from, Date to, Date date,
+ Date endTime, boolean isAllDay) {
+ return (isAllDay || !(to.getDay() == date.getDay()
+ && from.getDay() != to.getDay() && isMidnight(endTime)));
+ }
+
+ private void updateEventSlotIndex(CalendarEvent e, List<SimpleDayCell> cells) {
+ if (cells.isEmpty()) {
+ return;
+ }
+
+ if (e.getSlotIndex() == -1) {
+ // Update slot index
+ int newSlot = -1;
+ for (SimpleDayCell sdc : cells) {
+ int slot = sdc.getEventCount();
+ if (slot > newSlot) {
+ newSlot = slot;
+ }
+ }
+ newSlot++;
+
+ for (int i = 0; i < newSlot; i++) {
+ // check for empty slot
+ if (isSlotEmpty(e, i, cells)) {
+ newSlot = i;
+ break;
+ }
+ }
+ e.setSlotIndex(newSlot);
+ }
+ }
+
+ private void reDrawAllMonthEvents(boolean clearCells) {
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(row,
+ cell);
+ sdc.reDraw(clearCells);
+ }
+ }
+ }
+
+ private boolean isSlotEmpty(CalendarEvent addedEvent, int slotIndex,
+ List<SimpleDayCell> cells) {
+ for (SimpleDayCell sdc : cells) {
+ CalendarEvent e = sdc.getCalendarEvent(slotIndex);
+ if (e != null && !e.equals(addedEvent)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Remove a month event from the view
+ *
+ * @param target
+ * The event to remove
+ *
+ * @param repaintImmediately
+ * Should we repaint after the event was removed?
+ */
+ public void removeMonthEvent(CalendarEvent target,
+ boolean repaintImmediately) {
+ if (target != null && target.getSlotIndex() >= 0) {
+ // Remove event
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(
+ row, cell);
+ if (sdc == null) {
+ return;
+ }
+ sdc.removeEvent(target, repaintImmediately);
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates an event in the month grid
+ *
+ * @param changedEvent
+ * The event that has changed
+ */
+ public void updateEventToMonthGrid(CalendarEvent changedEvent) {
+ removeMonthEvent(changedEvent, true);
+ changedEvent.setSlotIndex(-1);
+ addEventToMonthGrid(changedEvent, true);
+ }
+
+ /**
+ * Sort the event by how long they are
+ *
+ * @param events
+ * The events to sort
+ * @return An array where the events has been sorted
+ */
+ public CalendarEvent[] sortEventsByDuration(Collection<CalendarEvent> events) {
+ CalendarEvent[] sorted = events
+ .toArray(new CalendarEvent[events.size()]);
+ Arrays.sort(sorted, getEventComparator());
+ return sorted;
+ }
+
+ /*
+ * Check if the given event occurs at the given date.
+ */
+ private boolean isEventInDay(Date eventWhen, Date eventTo, Date gridDate) {
+ if (eventWhen.compareTo(gridDate) <= 0
+ && eventTo.compareTo(gridDate) >= 0) {
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Re-render the week grid
+ *
+ * @param daysCount
+ * The amount of days to include in the week
+ * @param days
+ * The days
+ * @param today
+ * Todays date
+ * @param realDayNames
+ * The names of the dates
+ */
+ @SuppressWarnings("deprecation")
+ public void updateWeekGrid(int daysCount, List<CalendarDay> days,
+ Date today, String[] realDayNames) {
+ weekGrid.setFirstHour(getFirstHourOfTheDay());
+ weekGrid.setLastHour(getLastHourOfTheDay());
+ weekGrid.getTimeBar().updateTimeBar(is24HFormat());
+
+ dayToolbar.clear();
+ dayToolbar.addBackButton();
+ dayToolbar.setVerticalSized(isHeightUndefined);
+ dayToolbar.setHorizontalSized(isWidthUndefined);
+ weekGrid.clearDates();
+ weekGrid.setDisabled(isDisabledOrReadOnly());
+
+ for (CalendarDay day : days) {
+ String date = day.getDate();
+ String localized_date_format = day.getLocalizedDateFormat();
+ Date d = dateformat_date.parse(date);
+ int dayOfWeek = day.getDayOfWeek();
+ if (dayOfWeek < getFirstDayNumber()
+ || dayOfWeek > getLastDayNumber()) {
+ continue;
+ }
+ boolean isToday = false;
+ int dayOfMonth = d.getDate();
+ if (today.getDate() == dayOfMonth && today.getYear() == d.getYear()
+ && today.getMonth() == d.getMonth()) {
+ isToday = true;
+ }
+ dayToolbar.add(realDayNames[dayOfWeek - 1], date,
+ localized_date_format, isToday ? "today" : null);
+ weeklyLongEvents.addDate(d);
+ weekGrid.addDate(d);
+ if (isToday) {
+ weekGrid.setToday(d, today);
+ }
+ }
+ dayToolbar.addNextButton();
+ }
+
+ /**
+ * Updates the events in the Month view
+ *
+ * @param daysCount
+ * How many days there are
+ * @param daysUidl
+ *
+ * @param today
+ * Todays date
+ */
+ @SuppressWarnings("deprecation")
+ public void updateMonthGrid(int daysCount, List<CalendarDay> days,
+ Date today) {
+ int columns = getLastDayNumber() - getFirstDayNumber() + 1;
+ rows = (int) Math.ceil(daysCount / (double) 7);
+
+ monthGrid = new MonthGrid(this, rows, columns);
+ monthGrid.setEnabled(!isDisabledOrReadOnly());
+ weekToolbar.removeAllRows();
+ int pos = 0;
+ boolean monthNameDrawn = true;
+ boolean firstDayFound = false;
+ boolean lastDayFound = false;
+
+ for (CalendarDay day : days) {
+ String date = day.getDate();
+ Date d = dateformat_date.parse(date);
+ int dayOfWeek = day.getDayOfWeek();
+ int week = day.getWeek();
+
+ int dayOfMonth = d.getDate();
+
+ // reset at start of each month
+ if (dayOfMonth == 1) {
+ monthNameDrawn = false;
+ if (firstDayFound) {
+ lastDayFound = true;
+ }
+ firstDayFound = true;
+ }
+
+ if (dayOfWeek < getFirstDayNumber()
+ || dayOfWeek > getLastDayNumber()) {
+ continue;
+ }
+ int y = (pos / columns);
+ int x = pos - (y * columns);
+ if (x == 0 && daysCount > 7) {
+ // Add week to weekToolbar for navigation
+ weekToolbar.addWeek(week, d.getYear());
+ }
+ final SimpleDayCell cell = new SimpleDayCell(this, y, x);
+ cell.setMonthGrid(monthGrid);
+ cell.setDate(d);
+ cell.addDomHandler(new ContextMenuHandler() {
+ public void onContextMenu(ContextMenuEvent event) {
+ if (mouseEventListener != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ mouseEventListener.contextMenu(event, cell);
+ }
+ }
+ }, ContextMenuEvent.getType());
+
+ if (!firstDayFound) {
+ cell.addStyleDependentName("prev-month");
+ } else if (lastDayFound) {
+ cell.addStyleDependentName("next-month");
+ }
+
+ if (dayOfMonth >= 1 && !monthNameDrawn) {
+ cell.setMonthNameVisible(true);
+ monthNameDrawn = true;
+ }
+
+ if (today.getDate() == dayOfMonth && today.getYear() == d.getYear()
+ && today.getMonth() == d.getMonth()) {
+ cell.setToday(true);
+
+ }
+ monthGrid.setWidget(y, x, cell);
+ pos++;
+ }
+ }
+
+ public void setSizeForChildren(int newWidth, int newHeight) {
+ intWidth = newWidth;
+ intHeight = newHeight;
+ isWidthUndefined = intWidth == -1;
+ dayToolbar.setVerticalSized(isHeightUndefined);
+ dayToolbar.setHorizontalSized(isWidthUndefined);
+ recalculateWidths();
+ recalculateHeights();
+ }
+
+ /**
+ * Recalculates the heights of the sub-components in the calendar
+ */
+ protected void recalculateHeights() {
+ if (monthGrid != null) {
+
+ if (intHeight == -1) {
+ monthGrid.addStyleDependentName("sizedheight");
+ } else {
+ monthGrid.removeStyleDependentName("sizedheight");
+ }
+
+ monthGrid.updateCellSizes(intWidth - weekToolbar.getOffsetWidth(),
+ intHeight - nameToolbar.getOffsetHeight());
+ weekToolbar.setHeightPX((intHeight == -1) ? intHeight : intHeight
+ - nameToolbar.getOffsetHeight());
+
+ } else if (weekGrid != null) {
+ weekGrid.setHeightPX((intHeight == -1) ? intHeight : intHeight
+ - weeklyLongEvents.getOffsetHeight()
+ - dayToolbar.getOffsetHeight());
+ }
+ }
+
+ /**
+ * Recalculates the widths of the sub-components in the calendar
+ */
+ protected void recalculateWidths() {
+ if (!isWidthUndefined) {
+ nameToolbar.setWidthPX(intWidth);
+ dayToolbar.setWidthPX(intWidth);
+
+ if (monthGrid != null) {
+ monthGrid.updateCellSizes(
+ intWidth - weekToolbar.getOffsetWidth(), intHeight
+ - nameToolbar.getOffsetHeight());
+ } else if (weekGrid != null) {
+ weekGrid.setWidthPX(intWidth);
+ weeklyLongEvents.setWidthPX(weekGrid.getInternalWidth());
+ }
+ } else {
+ dayToolbar.setWidthPX(intWidth);
+ nameToolbar.setWidthPX(intWidth);
+
+ if (monthGrid != null) {
+ if (intWidth == -1) {
+ monthGrid.addStyleDependentName("sizedwidth");
+
+ } else {
+ monthGrid.removeStyleDependentName("sizedwidth");
+ }
+ } else if (weekGrid != null) {
+ weekGrid.setWidthPX(intWidth);
+ weeklyLongEvents.setWidthPX(weekGrid.getInternalWidth());
+ }
+ }
+ }
+
+ /**
+ * Get the date format used to format dates only (excludes time)
+ *
+ * @return
+ */
+ public DateTimeFormat getDateFormat() {
+ return dateformat_date;
+ }
+
+ /**
+ * Get the time format used to format time only (excludes date)
+ *
+ * @return
+ */
+ public DateTimeFormat getTimeFormat() {
+ if (is24HFormat()) {
+ return time24format_date;
+ }
+ return time12format_date;
+ }
+
+ /**
+ * Get the date and time format to format the dates (includes both date and
+ * time)
+ *
+ * @return
+ */
+ public DateTimeFormat getDateTimeFormat() {
+ return dateformat_datetime;
+ }
+
+ /**
+ * Is the calendar either disabled or readonly
+ *
+ * @return
+ */
+ public boolean isDisabledOrReadOnly() {
+ return disabled || readOnly;
+ }
+
+ /**
+ * Is the component disabled
+ */
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ /**
+ * Is the component disabled
+ *
+ * @param disabled
+ * True if disabled
+ */
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ /**
+ * Is the component read-only
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Is the component read-only
+ *
+ * @param readOnly
+ * True if component is readonly
+ */
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * Get the month grid component
+ *
+ * @return
+ */
+ public MonthGrid getMonthGrid() {
+ return monthGrid;
+ }
+
+ /**
+ * Get he week grid component
+ *
+ * @return
+ */
+ public WeekGrid getWeekGrid() {
+ return weekGrid;
+ }
+
+ /**
+ * Calculates correct size for all cells (size / amount of cells ) and
+ * distributes any overflow over all the cells.
+ *
+ * @param totalSize
+ * the total amount of size reserved for all cells
+ * @param numberOfCells
+ * the number of cells
+ * @param sizeModifier
+ * a modifier which is applied to all cells before distributing
+ * the overflow
+ * @return an integer array that contains the correct size for each cell
+ */
+ public static int[] distributeSize(int totalSize, int numberOfCells,
+ int sizeModifier) {
+ int[] cellSizes = new int[numberOfCells];
+ int startingSize = totalSize / numberOfCells;
+ int cellSizeOverFlow = totalSize % numberOfCells;
+
+ for (int i = 0; i < numberOfCells; i++) {
+ cellSizes[i] = startingSize + sizeModifier;
+ }
+
+ // distribute size overflow amongst all slots
+ int j = 0;
+ while (cellSizeOverFlow > 0) {
+ cellSizes[j]++;
+ cellSizeOverFlow--;
+ j++;
+ if (j >= numberOfCells) {
+ j = 0;
+ }
+ }
+
+ // cellSizes[numberOfCells - 1] += cellSizeOverFlow;
+
+ return cellSizes;
+ }
+
+ /**
+ * Returns a comparator which can compare calendar events.
+ *
+ * @return
+ */
+ public static Comparator<CalendarEvent> getEventComparator() {
+ return new Comparator<CalendarEvent>() {
+
+ public int compare(CalendarEvent o1, CalendarEvent o2) {
+ if (o1.isAllDay() != o2.isAllDay()) {
+ if (o2.isAllDay()) {
+ return 1;
+ }
+ return -1;
+ }
+
+ Long d1 = o1.getRangeInMilliseconds();
+ Long d2 = o2.getRangeInMilliseconds();
+ int r = 0;
+ if (!d1.equals(0L) && !d2.equals(0L)) {
+ r = d2.compareTo(d1);
+ return (r == 0) ? ((Integer) o2.getIndex()).compareTo(o1
+ .getIndex()) : r;
+ }
+
+ if (d2.equals(0L) && d1.equals(0L)) {
+ return ((Integer) o2.getIndex()).compareTo(o1.getIndex());
+ } else if (d2.equals(0L) && d1 >= DateConstants.DAYINMILLIS) {
+ return -1;
+ } else if (d2.equals(0L) && d1 < DateConstants.DAYINMILLIS) {
+ return 1;
+ } else if (d1.equals(0L) && d2 >= DateConstants.DAYINMILLIS) {
+ return 1;
+ } else if (d1.equals(0L) && d2 < DateConstants.DAYINMILLIS) {
+ return -1;
+ }
+ r = d2.compareTo(d1);
+ return (r == 0) ? ((Integer) o2.getIndex()).compareTo(o1
+ .getIndex()) : r;
+ }
+ };
+ }
+
+ /**
+ * Is the date at midnight
+ *
+ * @param date
+ * The date to check
+ *
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean isMidnight(Date date) {
+ return (date.getHours() == 0 && date.getMinutes() == 0 && date
+ .getSeconds() == 0);
+ }
+
+ /**
+ * Are the dates equal (uses second resolution)
+ *
+ * @param date1
+ * The first the to compare
+ * @param date2
+ * The second date to compare
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean areDatesEqualToSecond(Date date1, Date date2) {
+ return date1.getYear() == date2.getYear()
+ && date1.getMonth() == date2.getMonth()
+ && date1.getDay() == date2.getDay()
+ && date1.getHours() == date2.getHours()
+ && date1.getSeconds() == date2.getSeconds();
+ }
+
+ /**
+ * Is the calendar event zero seconds long and is occurring at midnight
+ *
+ * @param event
+ * The event to check
+ * @return
+ */
+ public static boolean isZeroLengthMidnightEvent(CalendarEvent event) {
+ return areDatesEqualToSecond(event.getStartTime(), event.getEndTime())
+ && isMidnight(event.getEndTime());
+ }
+
+ /**
+ * Should the 24h time format be used
+ *
+ * @param format
+ * True if the 24h format should be used else the 12h format is
+ * used
+ */
+ public void set24HFormat(boolean format) {
+ this.format = format;
+ }
+
+ /**
+ * Is the 24h time format used
+ */
+ public boolean is24HFormat() {
+ return format;
+ }
+
+ /**
+ * Set the names of the week days
+ *
+ * @param names
+ * The names of the days (Monday, Thursday,...)
+ */
+ public void setDayNames(String[] names) {
+ assert (names.length == 7);
+ dayNames = names;
+ }
+
+ /**
+ * Get the names of the week days
+ */
+ public String[] getDayNames() {
+ return dayNames;
+ }
+
+ /**
+ * Set the names of the months
+ *
+ * @param names
+ * The names of the months (January, February,...)
+ */
+ public void setMonthNames(String[] names) {
+ assert (names.length == 12);
+ monthNames = names;
+ }
+
+ /**
+ * Get the month names
+ */
+ public String[] getMonthNames() {
+ return monthNames;
+ }
+
+ /**
+ * Set the number when a week starts
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setFirstDayNumber(int dayNumber) {
+ assert (dayNumber >= 1 && dayNumber <= 7);
+ firstDay = dayNumber;
+ }
+
+ /**
+ * Get the number when a week starts
+ */
+ public int getFirstDayNumber() {
+ return firstDay;
+ }
+
+ /**
+ * Set the number when a week ends
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setLastDayNumber(int dayNumber) {
+ assert (dayNumber >= 1 && dayNumber <= 7);
+ lastDay = dayNumber;
+ }
+
+ /**
+ * Get the number when a week ends
+ */
+ public int getLastDayNumber() {
+ return lastDay;
+ }
+
+ /**
+ * Set the number when a week starts
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setFirstHourOfTheDay(int hour) {
+ assert (hour >= 0 && hour <= 23);
+ firstHour = hour;
+ }
+
+ /**
+ * Get the number when a week starts
+ */
+ public int getFirstHourOfTheDay() {
+ return firstHour;
+ }
+
+ /**
+ * Set the number when a week ends
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setLastHourOfTheDay(int hour) {
+ assert (hour >= 0 && hour <= 23);
+ lastHour = hour;
+ }
+
+ /**
+ * Get the number when a week ends
+ */
+ public int getLastHourOfTheDay() {
+ return lastHour;
+ }
+
+ /**
+ * Re-renders the whole week view
+ *
+ * @param scroll
+ * The amount of pixels to scroll the week view
+ * @param today
+ * Todays date
+ * @param daysInMonth
+ * How many days are there in the month
+ * @param firstDayOfWeek
+ * The first day of the week
+ * @param events
+ * The events to render
+ */
+ public void updateWeekView(int scroll, Date today, int daysInMonth,
+ int firstDayOfWeek, Collection<CalendarEvent> events,
+ List<CalendarDay> days) {
+
+ while (outer.getWidgetCount() > 0) {
+ outer.remove(0);
+ }
+
+ monthGrid = null;
+ String[] realDayNames = new String[getDayNames().length];
+ int j = 0;
+
+ if (firstDayOfWeek == 2) {
+ for (int i = 1; i < getDayNames().length; i++) {
+ realDayNames[j++] = getDayNames()[i];
+ }
+ realDayNames[j] = getDayNames()[0];
+ } else {
+ for (int i = 0; i < getDayNames().length; i++) {
+ realDayNames[j++] = getDayNames()[i];
+ }
+
+ }
+
+ weeklyLongEvents = new WeeklyLongEvents(this);
+ if (weekGrid == null) {
+ weekGrid = new WeekGrid(this, is24HFormat());
+ }
+ updateWeekGrid(daysInMonth, days, today, realDayNames);
+ updateEventsToWeekGrid(sortEventsByDuration(events));
+ outer.add(dayToolbar, DockPanel.NORTH);
+ outer.add(weeklyLongEvents, DockPanel.NORTH);
+ outer.add(weekGrid, DockPanel.SOUTH);
+ weekGrid.setVerticalScrollPosition(scroll);
+ }
+
+ /**
+ * Re-renders the whole month view
+ *
+ * @param firstDayOfWeek
+ * The first day of the week
+ * @param today
+ * Todays date
+ * @param daysInMonth
+ * Amount of days in the month
+ * @param events
+ * The events to render
+ * @param days
+ * The day information
+ */
+ public void updateMonthView(int firstDayOfWeek, Date today,
+ int daysInMonth, Collection<CalendarEvent> events,
+ List<CalendarDay> days) {
+
+ // Remove all week numbers from bar
+ while (outer.getWidgetCount() > 0) {
+ outer.remove(0);
+ }
+
+ int firstDay = getFirstDayNumber();
+ int lastDay = getLastDayNumber();
+ int daysPerWeek = lastDay - firstDay + 1;
+ int j = 0;
+
+ String[] dayNames = getDayNames();
+ String[] realDayNames = new String[daysPerWeek];
+
+ if (firstDayOfWeek == 2) {
+ for (int i = firstDay; i < lastDay + 1; i++) {
+ if (i == 7) {
+ realDayNames[j++] = dayNames[0];
+ } else {
+ realDayNames[j++] = dayNames[i];
+ }
+ }
+ } else {
+ for (int i = firstDay - 1; i < lastDay; i++) {
+ realDayNames[j++] = dayNames[i];
+ }
+ }
+
+ nameToolbar.setDayNames(realDayNames);
+
+ weeklyLongEvents = null;
+ weekGrid = null;
+
+ updateMonthGrid(daysInMonth, days, today);
+
+ outer.add(nameToolbar, DockPanel.NORTH);
+ outer.add(weekToolbar, DockPanel.WEST);
+ weekToolbar.updateCellHeights();
+ outer.add(monthGrid, DockPanel.CENTER);
+
+ updateEventsToMonthGrid(events, false);
+ }
+
+ private DateClickListener dateClickListener;
+
+ /**
+ * Sets the listener for listening to event clicks
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(DateClickListener listener) {
+ dateClickListener = listener;
+ }
+
+ /**
+ * Gets the listener for listening to event clicks
+ *
+ * @return
+ */
+ public DateClickListener getDateClickListener() {
+ return dateClickListener;
+ }
+
+ private ForwardListener forwardListener;
+
+ /**
+ * Set the listener which listens to forward events from the calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(ForwardListener listener) {
+ forwardListener = listener;
+ }
+
+ /**
+ * Get the listener which listens to forward events from the calendar
+ *
+ * @return
+ */
+ public ForwardListener getForwardListener() {
+ return forwardListener;
+ }
+
+ private BackwardListener backwardListener;
+
+ /**
+ * Set the listener which listens to backward events from the calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(BackwardListener listener) {
+ backwardListener = listener;
+ }
+
+ /**
+ * Set the listener which listens to backward events from the calendar
+ *
+ * @return
+ */
+ public BackwardListener getBackwardListener() {
+ return backwardListener;
+ }
+
+ private WeekClickListener weekClickListener;
+
+ /**
+ * Set the listener that listens to user clicking on the week numbers
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(WeekClickListener listener) {
+ weekClickListener = listener;
+ }
+
+ /**
+ * Get the listener that listens to user clicking on the week numbers
+ *
+ * @return
+ */
+ public WeekClickListener getWeekClickListener() {
+ return weekClickListener;
+ }
+
+ private RangeSelectListener rangeSelectListener;
+
+ /**
+ * Set the listener that listens to the user highlighting a region in the
+ * calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(RangeSelectListener listener) {
+ rangeSelectListener = listener;
+ }
+
+ /**
+ * Get the listener that listens to the user highlighting a region in the
+ * calendar
+ *
+ * @return
+ */
+ public RangeSelectListener getRangeSelectListener() {
+ return rangeSelectListener;
+ }
+
+ private EventClickListener eventClickListener;
+
+ /**
+ * Get the listener that listens to the user clicking on the events
+ */
+ public EventClickListener getEventClickListener() {
+ return eventClickListener;
+ }
+
+ /**
+ * Set the listener that listens to the user clicking on the events
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(EventClickListener listener) {
+ eventClickListener = listener;
+ }
+
+ private EventMovedListener eventMovedListener;
+
+ /**
+ * Get the listener that listens to when event is dragged to a new location
+ *
+ * @return
+ */
+ public EventMovedListener getEventMovedListener() {
+ return eventMovedListener;
+ }
+
+ /**
+ * Set the listener that listens to when event is dragged to a new location
+ *
+ * @param eventMovedListener
+ * The listener to use
+ */
+ public void setListener(EventMovedListener eventMovedListener) {
+ this.eventMovedListener = eventMovedListener;
+ }
+
+ private ScrollListener scrollListener;
+
+ /**
+ * Get the listener that listens to when the calendar widget is scrolled
+ *
+ * @return
+ */
+ public ScrollListener getScrollListener() {
+ return scrollListener;
+ }
+
+ /**
+ * Set the listener that listens to when the calendar widget is scrolled
+ *
+ * @param scrollListener
+ * The listener to use
+ */
+ public void setListener(ScrollListener scrollListener) {
+ this.scrollListener = scrollListener;
+ }
+
+ private EventResizeListener eventResizeListener;
+
+ /**
+ * Get the listener that listens to when an events time limits are being
+ * adjusted
+ *
+ * @return
+ */
+ public EventResizeListener getEventResizeListener() {
+ return eventResizeListener;
+ }
+
+ /**
+ * Set the listener that listens to when an events time limits are being
+ * adjusted
+ *
+ * @param eventResizeListener
+ * The listener to use
+ */
+ public void setListener(EventResizeListener eventResizeListener) {
+ this.eventResizeListener = eventResizeListener;
+ }
+
+ private MouseEventListener mouseEventListener;
+ private boolean forwardNavigationEnabled = true;
+ private boolean backwardNavigationEnabled = true;
+
+ /**
+ * Get the listener that listen to mouse events
+ *
+ * @return
+ */
+ public MouseEventListener getMouseEventListener() {
+ return mouseEventListener;
+ }
+
+ /**
+ * Set the listener that listen to mouse events
+ *
+ * @param mouseEventListener
+ * The listener to use
+ */
+ public void setListener(MouseEventListener mouseEventListener) {
+ this.mouseEventListener = mouseEventListener;
+ }
+
+ /**
+ * Is selecting a range allowed?
+ */
+ public boolean isRangeSelectAllowed() {
+ return rangeSelectAllowed;
+ }
+
+ /**
+ * Set selecting a range allowed
+ *
+ * @param rangeSelectAllowed
+ * Should selecting a range be allowed
+ */
+ public void setRangeSelectAllowed(boolean rangeSelectAllowed) {
+ this.rangeSelectAllowed = rangeSelectAllowed;
+ }
+
+ /**
+ * Is moving a range allowed
+ *
+ * @return
+ */
+ public boolean isRangeMoveAllowed() {
+ return rangeMoveAllowed;
+ }
+
+ /**
+ * Is moving a range allowed
+ *
+ * @param rangeMoveAllowed
+ * Is it allowed
+ */
+ public void setRangeMoveAllowed(boolean rangeMoveAllowed) {
+ this.rangeMoveAllowed = rangeMoveAllowed;
+ }
+
+ /**
+ * Is resizing an event allowed
+ */
+ public boolean isEventResizeAllowed() {
+ return eventResizeAllowed;
+ }
+
+ /**
+ * Is resizing an event allowed
+ *
+ * @param eventResizeAllowed
+ * True if allowed false if not
+ */
+ public void setEventResizeAllowed(boolean eventResizeAllowed) {
+ this.eventResizeAllowed = eventResizeAllowed;
+ }
+
+ /**
+ * Is moving an event allowed
+ */
+ public boolean isEventMoveAllowed() {
+ return eventMoveAllowed;
+ }
+
+ /**
+ * Is moving an event allowed
+ *
+ * @param eventMoveAllowed
+ * True if moving is allowed, false if not
+ */
+ public void setEventMoveAllowed(boolean eventMoveAllowed) {
+ this.eventMoveAllowed = eventMoveAllowed;
+ }
+
+ public boolean isBackwardNavigationEnabled() {
+ return backwardNavigationEnabled;
+ }
+
+ public void setBackwardNavigationEnabled(boolean enabled) {
+ backwardNavigationEnabled = enabled;
+ }
+
+ public boolean isForwardNavigationEnabled() {
+ return forwardNavigationEnabled;
+ }
+
+ public void setForwardNavigationEnabled(boolean enabled) {
+ forwardNavigationEnabled = enabled;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java
new file mode 100644
index 0000000000..120a65d842
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.Action;
+import com.vaadin.client.ui.ActionOwner;
+import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.client.ui.VCalendar.BackwardListener;
+import com.vaadin.client.ui.VCalendar.DateClickListener;
+import com.vaadin.client.ui.VCalendar.EventClickListener;
+import com.vaadin.client.ui.VCalendar.EventMovedListener;
+import com.vaadin.client.ui.VCalendar.EventResizeListener;
+import com.vaadin.client.ui.VCalendar.ForwardListener;
+import com.vaadin.client.ui.VCalendar.MouseEventListener;
+import com.vaadin.client.ui.VCalendar.RangeSelectListener;
+import com.vaadin.client.ui.VCalendar.WeekClickListener;
+import com.vaadin.client.ui.calendar.schedule.CalendarDay;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.client.ui.calendar.schedule.DateCell;
+import com.vaadin.client.ui.calendar.schedule.DateCell.DateCellSlot;
+import com.vaadin.client.ui.calendar.schedule.DateUtil;
+import com.vaadin.client.ui.calendar.schedule.DateCellDayEvent;
+import com.vaadin.client.ui.calendar.schedule.HasTooltipKey;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.calendar.schedule.dd.CalendarDropHandler;
+import com.vaadin.client.ui.dd.VHasDropHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.shared.ui.calendar.CalendarClientRpc;
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.CalendarState;
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.Calendar;
+
+/**
+ * Handles communication between Calendar on the server side and
+ * {@link VCalendar} on the client side.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@Connect(value = Calendar.class, loadStyle = LoadStyle.LAZY)
+public class CalendarConnector extends AbstractComponentConnector implements
+ VHasDropHandler, ActionOwner, SimpleManagedLayout {
+
+ private CalendarServerRpc rpc = RpcProxy.create(CalendarServerRpc.class,
+ this);
+
+ private CalendarDropHandler dropHandler;
+
+ private final HashMap<String, String> actionMap = new HashMap<String, String>();
+ private HashMap<Object, String> tooltips = new HashMap<Object, String>();
+
+ /**
+ *
+ */
+ public CalendarConnector() {
+
+ // Listen to events
+ registerListeners();
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ registerRpc(CalendarClientRpc.class, new CalendarClientRpc() {
+ @Override
+ public void scroll(int scrollPosition) {
+ // TODO widget scroll
+ }
+ });
+ getLayoutManager().registerDependency(this, getWidget().getElement());
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ getLayoutManager().unregisterDependency(this, getWidget().getElement());
+ }
+
+ @Override
+ public VCalendar getWidget() {
+ return (VCalendar) super.getWidget();
+ }
+
+ @Override
+ public CalendarState getState() {
+ return (CalendarState) super.getState();
+ }
+
+ /**
+ * Registers listeners on the calendar so server can be notified of the
+ * events
+ */
+ protected void registerListeners() {
+ getWidget().setListener(new DateClickListener() {
+ public void dateClick(String date) {
+ if (!getWidget().isDisabledOrReadOnly()
+ && hasEventListener(CalendarEventId.DATECLICK)) {
+ rpc.dateClick(date);
+ }
+ }
+ });
+ getWidget().setListener(new ForwardListener() {
+ public void forward() {
+ if (hasEventListener(CalendarEventId.FORWARD)) {
+ rpc.forward();
+ }
+ }
+ });
+ getWidget().setListener(new BackwardListener() {
+ public void backward() {
+ if (hasEventListener(CalendarEventId.BACKWARD)) {
+ rpc.backward();
+ }
+ }
+ });
+ getWidget().setListener(new RangeSelectListener() {
+ public void rangeSelected(String value) {
+ if (hasEventListener(CalendarEventId.RANGESELECT)) {
+ rpc.rangeSelect(value);
+ }
+ }
+ });
+ getWidget().setListener(new WeekClickListener() {
+ public void weekClick(String event) {
+ if (!getWidget().isDisabledOrReadOnly()
+ && hasEventListener(CalendarEventId.WEEKCLICK)) {
+ rpc.weekClick(event);
+ }
+ }
+ });
+ getWidget().setListener(new EventMovedListener() {
+ public void eventMoved(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTMOVE)) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DateUtil.formatClientSideDate(event.getStart()));
+ sb.append("-");
+ sb.append(DateUtil.formatClientSideTime(event
+ .getStartTime()));
+ rpc.eventMove(event.getIndex(), sb.toString());
+ }
+ }
+ });
+ getWidget().setListener(new EventResizeListener() {
+ public void eventResized(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTRESIZE)) {
+ StringBuilder buffer = new StringBuilder();
+
+ buffer.append(DateUtil.formatClientSideDate(event
+ .getStart()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event
+ .getStartTime()));
+
+ String newStartDate = buffer.toString();
+
+ buffer = new StringBuilder();
+ buffer.append(DateUtil.formatClientSideDate(event.getEnd()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event
+ .getEndTime()));
+
+ String newEndDate = buffer.toString();
+
+ rpc.eventResize(event.getIndex(), newStartDate, newEndDate);
+ }
+ }
+ });
+ getWidget().setListener(new VCalendar.ScrollListener() {
+ public void scroll(int scrollPosition) {
+ // This call is @Delayed (== non-immediate)
+ rpc.scroll(scrollPosition);
+ }
+ });
+ getWidget().setListener(new EventClickListener() {
+ public void eventClick(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTCLICK)) {
+ rpc.eventClick(event.getIndex());
+ }
+ }
+ });
+ getWidget().setListener(new MouseEventListener() {
+ public void contextMenu(ContextMenuEvent event, final Widget widget) {
+ final NativeEvent ne = event.getNativeEvent();
+ int left = ne.getClientX();
+ int top = ne.getClientY();
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ getClient().getContextMenu().showAt(new ActionOwner() {
+ public String getPaintableId() {
+ return CalendarConnector.this.getPaintableId();
+ }
+
+ public ApplicationConnection getClient() {
+ return CalendarConnector.this.getClient();
+ }
+
+ @SuppressWarnings("deprecation")
+ public Action[] getActions() {
+ if (widget instanceof SimpleDayCell) {
+ /*
+ * Month view
+ */
+ SimpleDayCell cell = (SimpleDayCell) widget;
+ Date start = new Date(cell.getDate().getYear(),
+ cell.getDate().getMonth(), cell.getDate()
+ .getDate(), 0, 0, 0);
+
+ Date end = new Date(cell.getDate().getYear(), cell
+ .getDate().getMonth(), cell.getDate()
+ .getDate(), 23, 59, 59);
+
+ return CalendarConnector.this.getActionsBetween(
+ start, end);
+ } else if (widget instanceof DateCell) {
+ /*
+ * Week and Day view
+ */
+ DateCell cell = (DateCell) widget;
+ int slotIndex = DOM.getChildIndex(
+ cell.getElement(), (Element) ne
+ .getEventTarget().cast());
+ DateCellSlot slot = cell.getSlot(slotIndex);
+ return CalendarConnector.this.getActionsBetween(
+ slot.getFrom(), slot.getTo());
+ } else if (widget instanceof DateCellDayEvent) {
+ /*
+ * Context menu on event
+ */
+ DateCellDayEvent dayEvent = (DateCellDayEvent) widget;
+ CalendarEvent event = dayEvent.getCalendarEvent();
+ Action[] actions = CalendarConnector.this
+ .getActionsBetween(event.getStartTime(),
+ event.getEndTime());
+ for (Action action : actions) {
+ ((VCalendarAction) action).setEvent(event);
+ }
+ return actions;
+
+ }
+ return null;
+ }
+ }, left, top);
+ }
+ });
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ CalendarState state = getState();
+ VCalendar widget = getWidget();
+ boolean monthView = state.days.size() > 7;
+
+ // Enable or disable the forward and backward navigation buttons
+ widget.setForwardNavigationEnabled(hasEventListener(CalendarEventId.FORWARD));
+ widget.setBackwardNavigationEnabled(hasEventListener(CalendarEventId.BACKWARD));
+
+ widget.set24HFormat(state.format24H);
+ widget.setDayNames(state.dayNames);
+ widget.setMonthNames(state.monthNames);
+ widget.setFirstDayNumber(state.firstVisibleDayOfWeek);
+ widget.setLastDayNumber(state.lastVisibleDayOfWeek);
+ widget.setFirstHourOfTheDay(state.firstHourOfDay);
+ widget.setLastHourOfTheDay(state.lastHourOfDay);
+ widget.setReadOnly(state.readOnly);
+ widget.setDisabled(!state.enabled);
+
+ widget.setRangeSelectAllowed(hasEventListener(CalendarEventId.RANGESELECT));
+ widget.setRangeMoveAllowed(hasEventListener(CalendarEventId.EVENTMOVE));
+ widget.setEventMoveAllowed(hasEventListener(CalendarEventId.EVENTMOVE));
+ widget.setEventResizeAllowed(hasEventListener(CalendarEventId.EVENTRESIZE));
+
+ List<CalendarState.Day> days = state.days;
+ List<CalendarState.Event> events = state.events;
+
+ if (monthView) {
+ updateMonthView(days, events);
+ } else {
+ updateWeekView(days, events);
+ }
+
+ updateSizes();
+
+ registerEventToolTips(state.events);
+ updateActionMap(state.actions);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal
+ * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection)
+ */
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ // check for DD -related access criteria
+ // Iterator<Object> childIterator = uidl.getChildIterator();
+ // while (childIterator.hasNext()) {
+ // UIDL child = (UIDL) childIterator.next();
+ //
+ // // Drag&drop
+ // if (ACCESSCRITERIA.equals(child.getTag())) {
+ // if (monthView
+ // && !(getDropHandler() instanceof CalendarMonthDropHandler)) {
+ // setDropHandler(new CalendarMonthDropHandler());
+ //
+ // } else if (!monthView
+ // && !(getDropHandler() instanceof CalendarWeekDropHandler)) {
+ // setDropHandler(new CalendarWeekDropHandler());
+ // }
+ //
+ // getDropHandler().setCalendarPaintable(this);
+ // getDropHandler().updateAcceptRules(child);
+ //
+ // } else {
+ // setDropHandler(null);
+ // }
+ //
+ // }
+ }
+
+ /**
+ * Returns the ApplicationConnection used to connect to the server side
+ */
+ @Override
+ public ApplicationConnection getClient() {
+ return getConnection();
+ }
+
+ /**
+ * Register the description of the events as tooltips. This way, any event
+ * displaying widget can use the event index as a key to display the
+ * tooltip.
+ */
+ private void registerEventToolTips(List<CalendarState.Event> events) {
+ for (CalendarState.Event e : events) {
+ if (e.description != null && !"".equals(e.description)) {
+ tooltips.put(e.index, e.description);
+ } else {
+ tooltips.remove(e.index);
+ }
+ }
+ }
+
+ @Override
+ public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) {
+ TooltipInfo tooltipInfo = null;
+ Widget w = Util.findWidget((Element) element, null);
+ if (w instanceof HasTooltipKey) {
+ tooltipInfo = GWT.create(TooltipInfo.class);
+ String title = tooltips.get(((HasTooltipKey) w).getTooltipKey());
+ tooltipInfo.setTitle(title != null ? title : "");
+ }
+ if (tooltipInfo == null) {
+ tooltipInfo = super.getTooltipInfo(element);
+ }
+ return tooltipInfo;
+ }
+
+ private void updateMonthView(List<CalendarState.Day> days,
+ List<CalendarState.Event> events) {
+ CalendarState state = getState();
+ getWidget().updateMonthView(state.firstDayOfWeek,
+ getWidget().getDateTimeFormat().parse(state.now), days.size(),
+ calendarEventListOf(events, state.format24H),
+ calendarDayListOf(days));
+ }
+
+ private void updateWeekView(List<CalendarState.Day> days,
+ List<CalendarState.Event> events) {
+ CalendarState state = getState();
+ getWidget().updateWeekView(state.scroll,
+ getWidget().getDateTimeFormat().parse(state.now), days.size(),
+ state.firstDayOfWeek,
+ calendarEventListOf(events, state.format24H),
+ calendarDayListOf(days));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler#getDropHandler()
+ */
+ public CalendarDropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ /**
+ * Set the drop handler
+ *
+ * @param dropHandler
+ * The drophandler to use
+ */
+ public void setDropHandler(CalendarDropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ private Action[] getActionsBetween(Date start, Date end) {
+ List<Action> actions = new ArrayList<Action>();
+ for (int i = 0; i < actionKeys.size(); i++) {
+ final String actionKey = actionKeys.get(i);
+ Date actionStartDate;
+ Date actionEndDate;
+ try {
+ actionStartDate = getActionStartDate(actionKey);
+ actionEndDate = getActionEndDate(actionKey);
+ } catch (ParseException pe) {
+ VConsole.error("Failed to parse action date");
+ continue;
+ }
+
+ boolean startIsValid = start.compareTo(actionStartDate) >= 0;
+ boolean endIsValid = end.compareTo(actionEndDate) <= 0;
+ if (startIsValid && endIsValid) {
+ VCalendarAction a = new VCalendarAction(this, rpc, actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ a.setActionStartDate(start);
+ a.setActionEndDate(end);
+ actions.add(a);
+ }
+ }
+
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ private List<String> actionKeys = new ArrayList<String>();
+
+ private void updateActionMap(List<CalendarState.Action> actions) {
+ actionMap.clear();
+ actionKeys.clear();
+
+ if (actions == null) {
+ return;
+ }
+
+ for (CalendarState.Action action : actions) {
+ String id = action.actionKey + "-" + action.startDate + "-"
+ + action.endDate;
+ actionMap.put(id + "_c", action.caption);
+ actionMap.put(id + "_s", action.startDate);
+ actionMap.put(id + "_e", action.endDate);
+ actionKeys.add(id);
+ if (action.iconKey != null) {
+ actionMap.put(id + "_i", getResourceUrl(action.iconKey));
+
+ } else {
+ actionMap.remove(id + "_i");
+ }
+ }
+ }
+
+ /**
+ * Get the text that is displayed for a context menu item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ */
+ public String getActionCaption(String actionKey) {
+ return actionMap.get(actionKey + "_c");
+ }
+
+ /**
+ * Get the icon url for a context menu item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ */
+ public String getActionIcon(String actionKey) {
+ return actionMap.get(actionKey + "_i");
+ }
+
+ /**
+ * Get the start date for an action item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ * @throws ParseException
+ */
+ public Date getActionStartDate(String actionKey) throws ParseException {
+ String dateStr = actionMap.get(actionKey + "_s");
+ DateTimeFormat formatter = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ return formatter.parse(dateStr);
+ }
+
+ /**
+ * Get the end date for an action item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ * @throws ParseException
+ */
+ public Date getActionEndDate(String actionKey) throws ParseException {
+ String dateStr = actionMap.get(actionKey + "_e");
+ DateTimeFormat formatter = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ return formatter.parse(dateStr);
+ }
+
+ /**
+ * Returns ALL currently registered events. Use {@link #getActions(Date)} to
+ * get the actions for a specific date
+ */
+ public Action[] getActions() {
+ List<Action> actions = new ArrayList<Action>();
+ for (int i = 0; i < actionKeys.size(); i++) {
+ final String actionKey = actionKeys.get(i);
+ final VCalendarAction a = new VCalendarAction(this, rpc, actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+
+ try {
+ a.setActionStartDate(getActionStartDate(actionKey));
+ a.setActionEndDate(getActionEndDate(actionKey));
+ } catch (ParseException pe) {
+ VConsole.error(pe);
+ }
+
+ actions.add(a);
+ }
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.ActionOwner#getPaintableId()
+ */
+ public String getPaintableId() {
+ return getConnectorId();
+ }
+
+ private List<CalendarEvent> calendarEventListOf(
+ List<CalendarState.Event> events, boolean format24h) {
+ List<CalendarEvent> list = new ArrayList<CalendarEvent>(events.size());
+ for (CalendarState.Event event : events) {
+ final String dateFrom = event.dateFrom;
+ final String dateTo = event.dateTo;
+ final String timeFrom = event.timeFrom;
+ final String timeTo = event.timeTo;
+ CalendarEvent calendarEvent = new CalendarEvent();
+ calendarEvent.setAllDay(event.allDay);
+ calendarEvent.setCaption(event.caption);
+ calendarEvent.setDescription(event.description);
+ calendarEvent.setStart(getWidget().getDateFormat().parse(dateFrom));
+ calendarEvent.setEnd(getWidget().getDateFormat().parse(dateTo));
+ calendarEvent.setFormat24h(format24h);
+ calendarEvent.setStartTime(getWidget().getDateTimeFormat().parse(
+ dateFrom + " " + timeFrom));
+ calendarEvent.setEndTime(getWidget().getDateTimeFormat().parse(
+ dateTo + " " + timeTo));
+ calendarEvent.setStyleName(event.styleName);
+ calendarEvent.setIndex(event.index);
+ list.add(calendarEvent);
+ }
+ return list;
+ }
+
+ private List<CalendarDay> calendarDayListOf(List<CalendarState.Day> days) {
+ List<CalendarDay> list = new ArrayList<CalendarDay>(days.size());
+ for (CalendarState.Day day : days) {
+ CalendarDay d = new CalendarDay(day.date, day.localizedDateFormat,
+ day.dayOfWeek, day.week);
+
+ list.add(d);
+ }
+ return list;
+ }
+
+ @Override
+ public void layout() {
+ updateSizes();
+ }
+
+ private void updateSizes() {
+ int height = getLayoutManager()
+ .getOuterHeight(getWidget().getElement());
+ int width = getLayoutManager().getOuterWidth(getWidget().getElement());
+
+ if (isUndefinedWidth()) {
+ width = -1;
+ }
+ if (isUndefinedHeight()) {
+ height = -1;
+ }
+
+ getWidget().setSizeForChildren(width, height);
+
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java b/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java
new file mode 100644
index 0000000000..2a529354e5
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.client.ui.Action;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Action performed by the calendar
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class VCalendarAction extends Action {
+
+ private CalendarServerRpc rpc;
+
+ private String actionKey = "";
+
+ private Date actionStartDate;
+
+ private Date actionEndDate;
+
+ private CalendarEvent event;
+
+ private final DateTimeFormat dateformat_datetime = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+
+ /**
+ *
+ * @param owner
+ */
+ public VCalendarAction(CalendarConnector owner) {
+ super(owner);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param owner
+ * The owner who trigger this kinds of events
+ * @param rpc
+ * The CalendarRpc which is used for executing actions
+ * @param key
+ * The unique action key which identifies this particular action
+ */
+ public VCalendarAction(CalendarConnector owner, CalendarServerRpc rpc,
+ String key) {
+ this(owner);
+ this.rpc = rpc;
+ actionKey = key;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.Action#execute()
+ */
+ @Override
+ public void execute() {
+ String startDate = dateformat_datetime.format(actionStartDate);
+ String endDate = dateformat_datetime.format(actionEndDate);
+
+ if (event == null) {
+ rpc.actionOnEmptyCell(actionKey.split("-")[0], startDate, endDate);
+ } else {
+ rpc.actionOnEvent(actionKey.split("-")[0], startDate, endDate,
+ event.getIndex());
+ }
+
+ owner.getClient().getContextMenu().hide();
+ }
+
+ /**
+ * Get the date and time when the action starts
+ *
+ * @return
+ */
+ public Date getActionStartDate() {
+ return actionStartDate;
+ }
+
+ /**
+ * Set the date when the actions start
+ *
+ * @param actionStartDate
+ * The date and time when the action starts
+ */
+ public void setActionStartDate(Date actionStartDate) {
+ this.actionStartDate = actionStartDate;
+ }
+
+ /**
+ * Get the date and time when the action ends
+ *
+ * @return
+ */
+ public Date getActionEndDate() {
+ return actionEndDate;
+ }
+
+ /**
+ * Set the date and time when the action ends
+ *
+ * @param actionEndDate
+ * The date and time when the action ends
+ */
+ public void setActionEndDate(Date actionEndDate) {
+ this.actionEndDate = actionEndDate;
+ }
+
+ public CalendarEvent getEvent() {
+ return event;
+ }
+
+ public void setEvent(CalendarEvent event) {
+ this.event = event;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java
new file mode 100644
index 0000000000..ca176c08c1
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+/**
+ * Utility class used to represent a day when updating views. Only used
+ * internally.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarDay {
+ private String date;
+ private String localizedDateFormat;
+ private int dayOfWeek;
+ private int week;
+
+ public CalendarDay(String date, String localizedDateFormat, int dayOfWeek,
+ int week) {
+ super();
+ this.date = date;
+ this.localizedDateFormat = localizedDateFormat;
+ this.dayOfWeek = dayOfWeek;
+ this.week = week;
+ }
+
+ public String getDate() {
+ return date;
+ }
+
+ public String getLocalizedDateFormat() {
+ return localizedDateFormat;
+ }
+
+ public int getDayOfWeek() {
+ return dayOfWeek;
+ }
+
+ public int getWeek() {
+ return week;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java
new file mode 100644
index 0000000000..e2c06d41ea
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * A client side implementation of a calendar event
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarEvent {
+ private int index;
+ private String caption;
+ private Date start, end;
+ private String styleName;
+ private Date startTime, endTime;
+ private String description;
+ private int slotIndex = -1;
+ private boolean format24h;
+
+ DateTimeFormat dateformat_date = DateTimeFormat.getFormat("h:mm a");
+ DateTimeFormat dateformat_date24 = DateTimeFormat.getFormat("H:mm");
+ private boolean allDay;
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ */
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ * @param style
+ */
+ public void setStyleName(String style) {
+ styleName = style;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ * @param start
+ */
+ public void setStart(Date start) {
+ this.start = start;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ * @return
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ * @param end
+ */
+ public void setEnd(Date end) {
+ this.end = end;
+ }
+
+ /**
+ * Returns the start time of the event
+ *
+ * @return Time embedded in the {@link Date} object
+ */
+ public Date getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Set the start time of the event
+ *
+ * @param startTime
+ * The time of the event. Use the time fields in the {@link Date}
+ * object
+ */
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ /**
+ * Get the end time of the event
+ *
+ * @return Time embedded in the {@link Date} object
+ */
+ public Date getEndTime() {
+ return endTime;
+ }
+
+ /**
+ * Set the end time of the event
+ *
+ * @param endTime
+ * Time embedded in the {@link Date} object
+ */
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+
+ /**
+ * Get the (server side) index of the event
+ *
+ * @return
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Get the index of the slot where the event in rendered
+ *
+ * @return
+ */
+ public int getSlotIndex() {
+ return slotIndex;
+ }
+
+ /**
+ * Set the index of the slot where the event in rendered
+ *
+ * @param index
+ * The index of the slot
+ */
+ public void setSlotIndex(int index) {
+ slotIndex = index;
+ }
+
+ /**
+ * Set the (server side) index of the event
+ *
+ * @param index
+ * The index
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Get the caption of the event. The caption is the text displayed in the
+ * calendar on the event.
+ *
+ * @return
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * Set the caption of the event. The caption is the text displayed in the
+ * calendar on the event.
+ *
+ * @param caption
+ * The visible caption of the event
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ /**
+ * Get the description of the event. The description is the text displayed
+ * when hoovering over the event with the mouse
+ *
+ * @return
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Set the description of the event. The description is the text displayed
+ * when hoovering over the event with the mouse
+ *
+ * @param description
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Does the event use the 24h time format
+ *
+ * @param format24h
+ * True if it uses the 24h format, false if it uses the 12h time
+ * format
+ */
+ public void setFormat24h(boolean format24h) {
+ this.format24h = format24h;
+ }
+
+ /**
+ * Is the event an all day event.
+ *
+ * @param allDay
+ * True if the event should be rendered all day
+ */
+ public void setAllDay(boolean allDay) {
+ this.allDay = allDay;
+ }
+
+ /**
+ * Is the event an all day event.
+ *
+ * @return
+ */
+ public boolean isAllDay() {
+ return allDay;
+ }
+
+ /**
+ * Get the time as a formatted string
+ *
+ * @return
+ */
+ public String getTimeAsText() {
+ if (format24h) {
+ return dateformat_date24.format(startTime);
+ } else {
+ return dateformat_date.format(startTime);
+ }
+ }
+
+ /**
+ * Get the amount of milliseconds between the start and end of the event
+ *
+ * @return
+ */
+ public long getRangeInMilliseconds() {
+ return getEndTime().getTime() - getStartTime().getTime();
+ }
+
+ /**
+ * Get the amount of minutes between the start and end of the event
+ *
+ * @return
+ */
+ public long getRangeInMinutes() {
+ return (getRangeInMilliseconds() / DateConstants.MINUTEINMILLIS);
+ }
+
+ /**
+ * Get the amount of minutes for the event on a specific day. This is useful
+ * if the event spans several days.
+ *
+ * @param targetDay
+ * The date to check
+ * @return
+ */
+ public long getRangeInMinutesForDay(Date targetDay) {
+ if (isTimeOnDifferentDays()) {
+ // Time range is on different days. Calculate the second day's
+ // range.
+ long range = (getEndTime().getTime() - getEnd().getTime())
+ / DateConstants.MINUTEINMILLIS;
+
+ if (getEnd().compareTo(targetDay) != 0) {
+ // Calculate first day's range.
+ return getRangeInMinutes() - range;
+ }
+
+ return range;
+ } else {
+ return getRangeInMinutes();
+ }
+ }
+
+ /**
+ * Does the event span several days
+ *
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public boolean isTimeOnDifferentDays() {
+ if (getEndTime().getTime() - getStart().getTime() > DateConstants.DAYINMILLIS) {
+ return true;
+ }
+
+ if (getStart().compareTo(getEnd()) != 0) {
+ if (getEndTime().getHours() == 0 && getEndTime().getMinutes() == 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java
new file mode 100644
index 0000000000..05e2a808fe
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java
@@ -0,0 +1,808 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.Util;
+
+public class DateCell extends FocusableComplexPanel implements
+ MouseDownHandler, MouseMoveHandler, MouseUpHandler, KeyDownHandler,
+ ContextMenuHandler {
+ private static final String DRAGEMPHASISSTYLE = " dragemphasis";
+ private Date date;
+ private int width;
+ private int eventRangeStart = -1;
+ private int eventRangeStop = -1;
+ final WeekGrid weekgrid;
+ private boolean disabled = false;
+ private int height;
+ private final Element[] slotElements;
+ private final List<DateCellSlot> slots = new ArrayList<DateCell.DateCellSlot>();
+ private int[] slotElementHeights;
+ private int startingSlotHeight;
+ private Date today;
+ private Element todaybar;
+ private final List<HandlerRegistration> handlers;
+ private final int numberOfSlots;
+ private final int firstHour;
+ private final int lastHour;
+
+ public class DateCellSlot extends Widget {
+
+ private final DateCell cell;
+
+ private final Date from;
+
+ private final Date to;
+
+ public DateCellSlot(DateCell cell, Date from, Date to) {
+ setElement(DOM.createDiv());
+ getElement().setInnerHTML("&nbsp;");
+ this.cell = cell;
+ this.from = from;
+ this.to = to;
+ }
+
+ public Date getFrom() {
+ return from;
+ }
+
+ public Date getTo() {
+ return to;
+ }
+
+ public DateCell getParentCell() {
+ return cell;
+ }
+ }
+
+ public DateCell(WeekGrid parent, Date date) {
+ weekgrid = parent;
+ Element mainElement = DOM.createDiv();
+ setElement(mainElement);
+ makeFocusable();
+ setDate(date);
+
+ addStyleName("v-calendar-day-times");
+
+ handlers = new LinkedList<HandlerRegistration>();
+
+ // 2 slots / hour
+ firstHour = weekgrid.getFirstHour();
+ lastHour = weekgrid.getLastHour();
+ numberOfSlots = (lastHour - firstHour + 1) * 2;
+ long slotTime = Math.round(((lastHour - firstHour + 1) * 3600000.0)
+ / numberOfSlots);
+
+ slotElements = new Element[numberOfSlots];
+ slotElementHeights = new int[numberOfSlots];
+
+ slots.clear();
+ long start = getDate().getTime() + firstHour * 3600000;
+ long end = start + slotTime;
+ for (int i = 0; i < numberOfSlots; i++) {
+ DateCellSlot slot = new DateCellSlot(this, new Date(
+ start), new Date(end));
+ if (i % 2 == 0) {
+ slot.setStyleName("v-datecellslot-even");
+ } else {
+ slot.setStyleName("v-datecellslot");
+ }
+ Event.sinkEvents(slot.getElement(), Event.MOUSEEVENTS);
+ mainElement.appendChild(slot.getElement());
+ slotElements[i] = slot.getElement();
+ slots.add(slot);
+ start = end;
+ end = start + slotTime;
+ }
+
+ // Sink events for tooltip handling
+ Event.sinkEvents(mainElement, Event.MOUSEEVENTS);
+ }
+
+ public int getFirstHour() {
+ return firstHour;
+ }
+
+ public int getLastHour() {
+ return lastHour;
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+
+ handlers.add(addHandler(this, MouseDownEvent.getType()));
+ handlers.add(addHandler(this, MouseUpEvent.getType()));
+ handlers.add(addHandler(this, MouseMoveEvent.getType()));
+ handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
+ handlers.add(addKeyDownHandler(this));
+ }
+
+ @Override
+ protected void onDetach() {
+ for (HandlerRegistration handler : handlers) {
+ handler.removeHandler();
+ }
+ handlers.clear();
+
+ super.onDetach();
+ }
+
+ public int getSlotIndex(Element slotElement) {
+ for (int i = 0; i < slotElements.length; i++) {
+ if (slotElement == slotElements[i]) {
+ return i;
+ }
+ }
+
+ throw new IllegalArgumentException(
+ "Element not found in this DateCell");
+ }
+
+ public DateCellSlot getSlot(int index) {
+ return slots.get(index);
+ }
+
+ public int getNumberOfSlots() {
+ return numberOfSlots;
+ }
+
+ public void setTimeBarWidth(int timebarWidth) {
+ todaybar.getStyle().setWidth(timebarWidth, Unit.PX);
+ }
+
+ /**
+ * @param isHorizontalSized
+ * if true, this DateCell is sized with CSS and not via
+ * {@link #setWidthPX(int)}
+ */
+ public void setHorizontalSized(boolean isHorizontalSized) {
+ if (isHorizontalSized) {
+ addStyleDependentName("Hsized");
+
+ width = getOffsetWidth()
+ - Util.measureHorizontalBorder(getElement());
+ recalculateEventWidths();
+ } else {
+ removeStyleDependentName("Hsized");
+ }
+ }
+
+ /**
+ * @param isVerticalSized
+ * if true, this DateCell is sized with CSS and not via
+ * {@link #setHeightPX(int)}
+ */
+ public void setVerticalSized(boolean isVerticalSized) {
+ if (isVerticalSized) {
+ addStyleDependentName("Vsized");
+
+ // recalc heights&size for events. all other height sizes come
+ // from css
+ startingSlotHeight = slotElements[0].getOffsetHeight();
+ recalculateEventPositions();
+
+ if (isToday()) {
+ recalculateTimeBarPosition();
+ }
+
+ } else {
+ removeStyleDependentName("Vsized");
+ }
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setWidthPX(int cellWidth) {
+ width = cellWidth;
+ setWidth(cellWidth + "px");
+ recalculateEventWidths();
+ }
+
+ public void setHeightPX(int height, int[] cellHeights) {
+ this.height = height;
+ slotElementHeights = cellHeights;
+ setHeight(height + "px");
+ recalculateCellHeights();
+ recalculateEventPositions();
+ if (today != null) {
+ recalculateTimeBarPosition();
+ }
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private void recalculateTimeBarPosition() {
+ int h = today.getHours();
+ int m = today.getMinutes();
+ if (h >= firstHour && h <= lastHour) {
+ int pixelTop = weekgrid.getPixelTopFor(m + 60 * h);
+ todaybar.getStyle().clearDisplay();
+ todaybar.getStyle().setTop(pixelTop, Unit.PX);
+ } else {
+ todaybar.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ private void recalculateEventPositions() {
+ for (int i = 0; i < getWidgetCount(); i++) {
+ DateCellDayEvent dayEvent = (DateCellDayEvent) getWidget(i);
+ updatePositionFor(dayEvent, getDate(),
+ dayEvent.getCalendarEvent());
+ }
+ }
+
+ public void recalculateEventWidths() {
+ List<DateCellGroup> groups = new ArrayList<DateCellGroup>();
+
+ int count = getWidgetCount();
+
+ List<Integer> handled = new ArrayList<Integer>();
+
+ // Iterate through all events and group them. Events that overlaps
+ // with each other, are added to the same group.
+ for (int i = 0; i < count; i++) {
+ if (handled.contains(i)) {
+ continue;
+ }
+
+ DateCellGroup curGroup = getOverlappingEvents(i);
+ handled.addAll(curGroup.getItems());
+
+ boolean newGroup = true;
+ // No need to check other groups, if size equals the count
+ if (curGroup.getItems().size() != count) {
+ // Check other groups. When the whole group overlaps with
+ // other group, the group is merged to the other.
+ for (DateCellGroup g : groups) {
+
+ if (WeekGridMinuteTimeRange.doesOverlap(
+ curGroup.getDateRange(), g.getDateRange())) {
+ newGroup = false;
+ updateGroup(g, curGroup);
+ }
+ }
+ } else {
+ if (newGroup) {
+ groups.add(curGroup);
+ }
+ break;
+ }
+
+ if (newGroup) {
+ groups.add(curGroup);
+ }
+ }
+
+ drawDayEvents(groups);
+ }
+
+ private void recalculateCellHeights() {
+ startingSlotHeight = height / numberOfSlots;
+
+ for (int i = 0; i < slotElements.length; i++) {
+ slotElements[i].getStyle().setHeight(slotElementHeights[i],
+ Unit.PX);
+ }
+
+ Iterator<Widget> it = iterator();
+ while (it.hasNext()) {
+ Widget child = it.next();
+ if (child instanceof DateCellDayEvent) {
+ ((DateCellDayEvent) child).setSlotHeightInPX(getSlotHeight());
+ }
+
+ }
+ }
+
+ public int getSlotHeight() {
+ return startingSlotHeight;
+ }
+
+ public int getSlotBorder() {
+ return Util
+ .measureVerticalBorder((com.google.gwt.user.client.Element) slotElements[0]);
+ }
+
+ private void drawDayEvents(List<DateCellGroup> groups) {
+ for (DateCellGroup g : groups) {
+ int col = 0;
+ int colCount = 0;
+ List<Integer> order = new ArrayList<Integer>();
+ Map<Integer, Integer> columns = new HashMap<Integer, Integer>();
+ for (Integer eventIndex : g.getItems()) {
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(eventIndex);
+ d.setMoveWidth(width);
+
+ int freeSpaceCol = findFreeColumnSpaceOnLeft(
+ new WeekGridMinuteTimeRange(d.getCalendarEvent()
+ .getStartTime(), d.getCalendarEvent()
+ .getEndTime()), order, columns);
+ if (freeSpaceCol >= 0) {
+ col = freeSpaceCol;
+ columns.put(eventIndex, col);
+ int newOrderindex = 0;
+ for (Integer i : order) {
+ if (columns.get(i) >= col) {
+ newOrderindex = order.indexOf(i);
+ break;
+ }
+ }
+ order.add(newOrderindex, eventIndex);
+ } else {
+ // New column
+ col = colCount++;
+ columns.put(eventIndex, col);
+ order.add(eventIndex);
+ }
+ }
+
+ // Update widths and left position
+ int eventWidth = (width / colCount);
+ for (Integer index : g.getItems()) {
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(index);
+ d.getElement()
+ .getStyle()
+ .setMarginLeft((eventWidth * columns.get(index)),
+ Unit.PX);
+ d.setWidth(eventWidth + "px");
+ d.setSlotHeightInPX(getSlotHeight());
+ }
+ }
+ }
+
+ private int findFreeColumnSpaceOnLeft(WeekGridMinuteTimeRange dateRange,
+ List<Integer> order, Map<Integer, Integer> columns) {
+ int freeSpot = -1;
+ int skipIndex = -1;
+ for (Integer eventIndex : order) {
+ int col = columns.get(eventIndex);
+ if (col == skipIndex) {
+ continue;
+ }
+
+ if (freeSpot != -1 && freeSpot != col) {
+ // Free spot found
+ return freeSpot;
+ }
+
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(eventIndex);
+ WeekGridMinuteTimeRange nextRange = new WeekGridMinuteTimeRange(d
+ .getCalendarEvent().getStartTime(), d
+ .getCalendarEvent().getEndTime());
+
+ if (WeekGridMinuteTimeRange.doesOverlap(dateRange, nextRange)) {
+ skipIndex = col;
+ freeSpot = -1;
+ } else {
+ freeSpot = col;
+ }
+ }
+
+ return freeSpot;
+ }
+
+ /* Update top and bottom date range values. Add new index to the group. */
+ private void updateGroup(DateCellGroup targetGroup, DateCellGroup byGroup) {
+ Date newStart = targetGroup.getStart();
+ Date newEnd = targetGroup.getEnd();
+ if (byGroup.getStart().before(targetGroup.getStart())) {
+ newStart = byGroup.getEnd();
+ }
+ if (byGroup.getStart().after(targetGroup.getEnd())) {
+ newStart = byGroup.getStart();
+ }
+
+ targetGroup.setDateRange(new WeekGridMinuteTimeRange(newStart, newEnd));
+
+ for (Integer index : byGroup.getItems()) {
+ if (!targetGroup.getItems().contains(index)) {
+ targetGroup.add(index);
+ }
+ }
+ }
+
+ /**
+ * Returns all overlapping DayEvent indexes in the Group. Including the
+ * target.
+ *
+ * @param targetIndex
+ * Index of DayEvent in the current DateCell widget.
+ * @return Group that contains all Overlapping DayEvent indexes
+ */
+ public DateCellGroup getOverlappingEvents(int targetIndex) {
+ DateCellGroup g = new DateCellGroup(targetIndex);
+
+ int count = getWidgetCount();
+ DateCellDayEvent target = (DateCellDayEvent) getWidget(targetIndex);
+ WeekGridMinuteTimeRange targetRange = new WeekGridMinuteTimeRange(target
+ .getCalendarEvent().getStartTime(), target
+ .getCalendarEvent().getEndTime());
+ Date groupStart = targetRange.getStart();
+ Date groupEnd = targetRange.getEnd();
+
+ for (int i = 0; i < count; i++) {
+ if (targetIndex == i) {
+ continue;
+ }
+
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(i);
+ WeekGridMinuteTimeRange nextRange = new WeekGridMinuteTimeRange(d
+ .getCalendarEvent().getStartTime(), d
+ .getCalendarEvent().getEndTime());
+ if (WeekGridMinuteTimeRange.doesOverlap(targetRange, nextRange)) {
+ g.add(i);
+
+ // Update top & bottom values to the greatest
+ if (nextRange.getStart().before(targetRange.getStart())) {
+ groupStart = targetRange.getStart();
+ }
+ if (nextRange.getEnd().after(targetRange.getEnd())) {
+ groupEnd = targetRange.getEnd();
+ }
+ }
+ }
+
+ g.setDateRange(new WeekGridMinuteTimeRange(groupStart, groupEnd));
+ return g;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void addEvent(Date targetDay, CalendarEvent calendarEvent) {
+ Element main = getElement();
+ DateCellDayEvent dayEvent = new DateCellDayEvent(this, weekgrid, calendarEvent);
+ dayEvent.setSlotHeightInPX(getSlotHeight());
+ dayEvent.setDisabled(isDisabled());
+
+ if (startingSlotHeight > 0) {
+ updatePositionFor(dayEvent, targetDay, calendarEvent);
+ }
+
+ add(dayEvent, (com.google.gwt.user.client.Element) main);
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private void updatePositionFor(DateCellDayEvent dayEvent, Date targetDay,
+ CalendarEvent calendarEvent) {
+ if (canDisplay(calendarEvent)) {
+
+ dayEvent.getElement().getStyle().clearDisplay();
+
+ Date fromDt = calendarEvent.getStartTime();
+ int h = fromDt.getHours();
+ int m = fromDt.getMinutes();
+ long range = calendarEvent.getRangeInMinutesForDay(targetDay);
+
+ boolean onDifferentDays = calendarEvent.isTimeOnDifferentDays();
+ if (onDifferentDays) {
+ if (calendarEvent.getStart().compareTo(targetDay) != 0) {
+ // Current day slot is for the end date. Lets fix also
+ // the
+ // start & end times.
+ h = 0;
+ m = 0;
+ }
+ }
+
+ int startFromMinutes = (h * 60) + m;
+ dayEvent.updatePosition(startFromMinutes, range);
+
+ } else {
+ dayEvent.getElement().getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ public void addEvent(DateCellDayEvent dayEvent) {
+ Element main = getElement();
+ int index = 0;
+ List<CalendarEvent> events = new ArrayList<CalendarEvent>();
+
+ // events are the only widgets in this panel
+ // slots are just elements
+ for (; index < getWidgetCount(); index++) {
+ DateCellDayEvent dc = (DateCellDayEvent) getWidget(index);
+ dc.setDisabled(isDisabled());
+ events.add(dc.getCalendarEvent());
+ }
+ events.add(dayEvent.getCalendarEvent());
+
+ index = 0;
+ for (CalendarEvent e : weekgrid.getCalendar().sortEventsByDuration(
+ events)) {
+ if (e.equals(dayEvent.getCalendarEvent())) {
+ break;
+ }
+ index++;
+ }
+ this.insert(dayEvent, (com.google.gwt.user.client.Element) main,
+ index, true);
+ }
+
+ public void removeEvent(DateCellDayEvent dayEvent) {
+ remove(dayEvent);
+ }
+
+ /**
+ *
+ * @param event
+ * @return
+ */
+ // Date methods not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private boolean canDisplay(CalendarEvent event) {
+ Date eventStart = event.getStartTime();
+ Date eventEnd = event.getEndTime();
+
+ int eventStartHours = eventStart.getHours();
+ int eventEndHours = eventEnd.getHours();
+
+ return (eventStartHours <= lastHour)
+ && (eventEndHours >= firstHour);
+ }
+
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode == KeyCodes.KEY_ESCAPE && eventRangeStart > -1) {
+ cancelRangeSelect();
+ }
+ }
+
+ public void onMouseDown(MouseDownEvent event) {
+ if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
+ Element e = Element.as(event.getNativeEvent().getEventTarget());
+ if (e.getClassName().contains("reserved") || isDisabled()
+ || !weekgrid.getParentCalendar().isRangeSelectAllowed()) {
+ eventRangeStart = -1;
+ } else {
+ eventRangeStart = event.getY();
+ eventRangeStop = eventRangeStart;
+ Event.setCapture(getElement());
+ setFocus(true);
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+ Event.releaseCapture(getElement());
+ setFocus(false);
+ int dragDistance = Math.abs(eventRangeStart - event.getY());
+ if (dragDistance > 0 && eventRangeStart >= 0) {
+ Element main = getElement();
+ if (eventRangeStart > eventRangeStop) {
+ if (eventRangeStop <= -1) {
+ eventRangeStop = 0;
+ }
+ int temp = eventRangeStart;
+ eventRangeStart = eventRangeStop;
+ eventRangeStop = temp;
+ }
+
+ NodeList<Node> nodes = main.getChildNodes();
+
+ int slotStart = -1;
+ int slotEnd = -1;
+
+ // iterate over all child nodes, until we find first the start,
+ // and then the end
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.getItem(i);
+ boolean isRangeElement = element.getClassName().contains(
+ "v-daterange");
+
+ if (isRangeElement && slotStart == -1) {
+ slotStart = i;
+ slotEnd = i; // to catch one-slot selections
+
+ } else if (isRangeElement) {
+ slotEnd = i;
+
+ } else if (slotStart != -1 && slotEnd != -1) {
+ break;
+ }
+ }
+
+ clearSelectionRange();
+
+ int startMinutes = firstHour * 60 + slotStart * 30;
+ int endMinutes = (firstHour * 60) + (slotEnd + 1) * 30;
+ Date currentDate = getDate();
+ String yr = (currentDate.getYear() + 1900) + "-"
+ + (currentDate.getMonth() + 1) + "-"
+ + currentDate.getDate();
+ if (weekgrid.getCalendar().getRangeSelectListener() != null) {
+ weekgrid.getCalendar()
+ .getRangeSelectListener()
+ .rangeSelected(
+ yr + ":" + startMinutes + ":" + endMinutes);
+ }
+ eventRangeStart = -1;
+ } else {
+ // Click event
+ eventRangeStart = -1;
+ cancelRangeSelect();
+
+ }
+ }
+
+ public void onMouseMove(MouseMoveEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ if (eventRangeStart >= 0) {
+ int newY = event.getY();
+ int fromY = 0;
+ int toY = 0;
+ if (newY < eventRangeStart) {
+ fromY = newY;
+ toY = eventRangeStart;
+ } else {
+ fromY = eventRangeStart;
+ toY = newY;
+ }
+ Element main = getElement();
+ eventRangeStop = newY;
+ NodeList<Node> nodes = main.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element c = (Element) nodes.getItem(i);
+
+ if (todaybar != c) {
+
+ int elemStart = c.getOffsetTop();
+ int elemStop = elemStart + getSlotHeight();
+ if (elemStart >= fromY && elemStart <= toY) {
+ c.addClassName("v-daterange");
+ } else if (elemStop >= fromY && elemStop <= toY) {
+ c.addClassName("v-daterange");
+ } else if (elemStop >= fromY && elemStart <= toY) {
+ c.addClassName("v-daterange");
+ } else {
+ c.removeClassName("v-daterange");
+ }
+ }
+ }
+ }
+
+ event.preventDefault();
+ }
+
+ public void cancelRangeSelect() {
+ Event.releaseCapture(getElement());
+ setFocus(false);
+
+ clearSelectionRange();
+ }
+
+ private void clearSelectionRange() {
+ if (eventRangeStart > -1) {
+ // clear all "selected" class names
+ Element main = getElement();
+ NodeList<Node> nodes = main.getChildNodes();
+
+ for (int i = 0; i <= 47; i++) {
+ Element c = (Element) nodes.getItem(i);
+ if (c == null) {
+ continue;
+ }
+ c.removeClassName("v-daterange");
+ }
+
+ eventRangeStart = -1;
+ }
+ }
+
+ public void setToday(Date today, int width) {
+ this.today = today;
+ addStyleDependentName("today");
+ Element lastChild = (Element) getElement().getLastChild();
+ if (lastChild.getClassName().equals("v-calendar-current-time")) {
+ todaybar = lastChild;
+ } else {
+ todaybar = DOM.createDiv();
+ todaybar.setClassName("v-calendar-current-time");
+ getElement().appendChild(todaybar);
+ }
+
+ if (width != -1) {
+ todaybar.getStyle().setWidth(width, Unit.PX);
+ }
+
+ // position is calculated later, when we know the cell heights
+ }
+
+ public Element getTodaybarElement() {
+ return todaybar;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public void setDateColor(String styleName) {
+ this.setStyleName("v-calendar-datecell " + styleName);
+ }
+
+ public boolean isToday() {
+ return today != null;
+ }
+
+ public void addEmphasisStyle(
+ com.google.gwt.user.client.Element elementOver) {
+ String originalStylename = getStyleName(elementOver);
+ setStyleName(elementOver, originalStylename + DRAGEMPHASISSTYLE);
+ }
+
+ public void removeEmphasisStyle(
+ com.google.gwt.user.client.Element elementOver) {
+ String originalStylename = getStyleName(elementOver);
+ setStyleName(
+ elementOver,
+ originalStylename.substring(0, originalStylename.length()
+ - DRAGEMPHASISSTYLE.length()));
+ }
+
+ public void onContextMenu(ContextMenuEvent event) {
+ if (weekgrid.getCalendar().getMouseEventListener() != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ weekgrid.getCalendar().getMouseEventListener()
+ .contextMenu(event, DateCell.this);
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java
new file mode 100644
index 0000000000..f1b45c83c5
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * Internally used class by the Calendar
+ *
+ * since 7.1
+ */
+public class DateCellContainer extends FlowPanel implements
+ MouseDownHandler, MouseUpHandler {
+
+ private Date date;
+
+ private Widget clickTargetWidget;
+
+ private VCalendar calendar;
+
+ private static int borderWidth = -1;
+
+ public DateCellContainer() {
+ setStylePrimaryName("v-calendar-datecell");
+ }
+
+ public static int measureBorderWidth(DateCellContainer dc) {
+ if (borderWidth == -1) {
+ borderWidth = Util.measureHorizontalBorder(dc.getElement());
+ }
+ return borderWidth;
+ }
+
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public boolean hasEvent(int slotIndex) {
+ return hasDateCell(slotIndex)
+ && ((WeeklyLongEventsDateCell) getChildren().get(slotIndex)).getEvent() != null;
+ }
+
+ public boolean hasDateCell(int slotIndex) {
+ return (getChildren().size() - 1) >= slotIndex;
+ }
+
+ public WeeklyLongEventsDateCell getDateCell(int slotIndex) {
+ if (!hasDateCell(slotIndex)) {
+ addEmptyEventCells(slotIndex - (getChildren().size() - 1));
+ }
+ return (WeeklyLongEventsDateCell) getChildren().get(slotIndex);
+ }
+
+ public void addEmptyEventCells(int eventCount) {
+ for (int i = 0; i < eventCount; i++) {
+ addEmptyEventCell();
+ }
+ }
+
+ public void addEmptyEventCell() {
+ WeeklyLongEventsDateCell dateCell = new WeeklyLongEventsDateCell();
+ dateCell.addMouseDownHandler(this);
+ dateCell.addMouseUpHandler(this);
+ add(dateCell);
+ }
+
+ public void onMouseDown(MouseDownEvent event) {
+ clickTargetWidget = (Widget) event.getSource();
+
+ event.stopPropagation();
+ }
+
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getSource() == clickTargetWidget
+ && clickTargetWidget instanceof WeeklyLongEventsDateCell
+ && !calendar.isDisabledOrReadOnly()) {
+ CalendarEvent calendarEvent = ((WeeklyLongEventsDateCell) clickTargetWidget)
+ .getEvent();
+ if (calendar.getEventClickListener() != null) {
+ calendar.getEventClickListener().eventClick(calendarEvent);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
new file mode 100644
index 0000000000..039a00e25a
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class DateCellDayEvent extends FocusableHTML implements
+ MouseDownHandler, MouseUpHandler, MouseMoveHandler,
+ KeyDownHandler, ContextMenuHandler, HasTooltipKey {
+
+ private final DateCell dateCell;
+ private Element caption = null;
+ private final Element eventContent;
+ private CalendarEvent calendarEvent = null;
+ private HandlerRegistration moveRegistration;
+ private int startY = -1;
+ private int startX = -1;
+ private String moveWidth;
+ public static final int halfHourInMilliSeconds = 1800 * 1000;
+ private Date startDatetimeFrom;
+ private Date startDatetimeTo;
+ private boolean mouseMoveStarted;
+ private int top;
+ private int startYrelative;
+ private int startXrelative;
+ private boolean disabled;
+ private final WeekGrid weekGrid;
+ private com.google.gwt.user.client.Element topResizeBar;
+ private com.google.gwt.user.client.Element bottomResizeBar;
+ private Element clickTarget;
+ private final Integer eventIndex;
+ private int slotHeight;
+ private final List<HandlerRegistration> handlers;
+ private boolean mouseMoveCanceled;
+
+ public DateCellDayEvent(DateCell dateCell, WeekGrid parent, CalendarEvent event) {
+ super();
+ this.dateCell = dateCell;
+
+ handlers = new LinkedList<HandlerRegistration>();
+
+ setStylePrimaryName("v-calendar-event");
+ setCalendarEvent(event);
+
+ weekGrid = parent;
+
+ Style s = getElement().getStyle();
+ if (event.getStyleName().length() > 0) {
+ addStyleDependentName(event.getStyleName());
+ }
+ s.setPosition(Position.ABSOLUTE);
+
+ caption = DOM.createDiv();
+ caption.addClassName("v-calendar-event-caption");
+ getElement().appendChild(caption);
+
+ eventContent = DOM.createDiv();
+ eventContent.addClassName("v-calendar-event-content");
+ getElement().appendChild(eventContent);
+
+ VCalendar calendar = weekGrid.getCalendar();
+ if (weekGrid.getCalendar().isEventResizeAllowed()) {
+ topResizeBar = DOM.createDiv();
+ bottomResizeBar = DOM.createDiv();
+
+ topResizeBar.addClassName("v-calendar-event-resizetop");
+ bottomResizeBar
+ .addClassName("v-calendar-event-resizebottom");
+
+ getElement().appendChild(topResizeBar);
+ getElement().appendChild(bottomResizeBar);
+ }
+
+ eventIndex = event.getIndex();
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ handlers.add(addMouseDownHandler(this));
+ handlers.add(addMouseUpHandler(this));
+ handlers.add(addKeyDownHandler(this));
+ handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
+ }
+
+ @Override
+ protected void onDetach() {
+ for (HandlerRegistration handler : handlers) {
+ handler.removeHandler();
+ }
+ handlers.clear();
+ super.onDetach();
+ }
+
+ public void setSlotHeightInPX(int slotHeight) {
+ this.slotHeight = slotHeight;
+ }
+
+ public void updatePosition(long startFromMinutes,
+ long durationInMinutes) {
+ if (startFromMinutes < 0) {
+ startFromMinutes = 0;
+ }
+ top = weekGrid.getPixelTopFor((int) startFromMinutes);
+
+ getElement().getStyle().setTop(top, Unit.PX);
+ if (durationInMinutes > 0) {
+ int heightMinutes = weekGrid.getPixelLengthFor(
+ (int) startFromMinutes, (int) durationInMinutes);
+ setHeight(heightMinutes);
+ } else {
+ setHeight(-1);
+ }
+
+ boolean multiRowCaption = (durationInMinutes > 30);
+ updateCaptions(multiRowCaption);
+ }
+
+ public int getTop() {
+ return top;
+ }
+
+ public void setMoveWidth(int width) {
+ moveWidth = width + "px";
+ }
+
+ public void setHeight(int h) {
+ if (h == -1) {
+ getElement().getStyle().setProperty("height", "");
+ eventContent.getStyle().setProperty("height", "");
+ } else {
+ getElement().getStyle().setHeight(h, Unit.PX);
+ // FIXME measure the border height (2px) from the DOM
+ eventContent.getStyle().setHeight(h - 2, Unit.PX);
+ }
+ }
+
+ /**
+ * @param bigMode
+ * If false, event is so small that caption must be in
+ * time-row
+ */
+ private void updateCaptions(boolean bigMode) {
+ String separator = bigMode ? "<br />" : ": ";
+ caption.setInnerHTML("<span>" + calendarEvent.getTimeAsText()
+ + "</span>" + separator
+ + Util.escapeHTML(calendarEvent.getCaption()));
+ eventContent.setInnerHTML("");
+ }
+
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode == KeyCodes.KEY_ESCAPE && mouseMoveStarted) {
+ cancelMouseMove();
+ }
+ }
+
+ public void onMouseDown(MouseDownEvent event) {
+ startX = event.getClientX();
+ startY = event.getClientY();
+ if (isDisabled()
+ || event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ clickTarget = Element.as(event.getNativeEvent()
+ .getEventTarget());
+ mouseMoveCanceled = false;
+
+ if (weekGrid.getCalendar().isEventMoveAllowed()
+ || clickTargetsResize()) {
+ moveRegistration = addMouseMoveHandler(this);
+ setFocus(true);
+ try {
+ startYrelative = (int) ((double) event
+ .getRelativeY(caption) % slotHeight);
+ startXrelative = (event.getRelativeX(weekGrid
+ .getElement()) - weekGrid.timebar
+ .getOffsetWidth())
+ % getDateCellWidth();
+ } catch (Exception e) {
+ GWT.log("Exception calculating relative start position",
+ e);
+ }
+ mouseMoveStarted = false;
+ Style s = getElement().getStyle();
+ s.setZIndex(1000);
+ startDatetimeFrom = (Date) calendarEvent.getStartTime()
+ .clone();
+ startDatetimeTo = (Date) calendarEvent.getEndTime().clone();
+ Event.setCapture(getElement());
+ }
+
+ // make sure the right cursor is always displayed
+ if (clickTargetsResize()) {
+ addGlobalResizeStyle();
+ }
+
+ /*
+ * We need to stop the event propagation or else the WeekGrid
+ * range select will kick in
+ */
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ public void onMouseUp(MouseUpEvent event) {
+ if (mouseMoveCanceled) {
+ return;
+ }
+
+ Event.releaseCapture(getElement());
+ setFocus(false);
+ if (moveRegistration != null) {
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ }
+ int endX = event.getClientX();
+ int endY = event.getClientY();
+ int xDiff = startX - endX;
+ int yDiff = startY - endY;
+ startX = -1;
+ startY = -1;
+ mouseMoveStarted = false;
+ Style s = getElement().getStyle();
+ s.setZIndex(1);
+ if (!clickTargetsResize()) {
+ // check if mouse has moved over threshold of 3 pixels
+ boolean mouseMoved = (xDiff < -3 || xDiff > 3 || yDiff < -3 || yDiff > 3);
+
+ if (!weekGrid.getCalendar().isDisabledOrReadOnly()
+ && mouseMoved) {
+ // Event Move:
+ // - calendar must be enabled
+ // - calendar must not be in read-only mode
+ weekGrid.eventMoved(this);
+ } else if (!weekGrid.getCalendar().isDisabled()) {
+ // Event Click:
+ // - calendar must be enabled (read-only is allowed)
+ EventTarget et = event.getNativeEvent()
+ .getEventTarget();
+ Element e = Element.as(et);
+ if (e == caption || e == eventContent
+ || e.getParentElement() == caption) {
+ if (weekGrid.getCalendar().getEventClickListener() != null) {
+ weekGrid.getCalendar().getEventClickListener()
+ .eventClick(calendarEvent);
+ }
+ }
+ }
+
+ } else { // click targeted resize bar
+ removeGlobalResizeStyle();
+ if (weekGrid.getCalendar().getEventResizeListener() != null) {
+ weekGrid.getCalendar().getEventResizeListener()
+ .eventResized(calendarEvent);
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void onMouseMove(MouseMoveEvent event) {
+ if (startY < 0 && startX < 0) {
+ return;
+ }
+ if (isDisabled()) {
+ Event.releaseCapture(getElement());
+ mouseMoveStarted = false;
+ startY = -1;
+ startX = -1;
+ removeGlobalResizeStyle();
+ return;
+ }
+ int currentY = event.getClientY();
+ int currentX = event.getClientX();
+ int moveY = (currentY - startY);
+ int moveX = (currentX - startX);
+ if ((moveY < 5 && moveY > -6) && (moveX < 5 && moveX > -6)) {
+ return;
+ }
+ if (!mouseMoveStarted) {
+ setWidth(moveWidth);
+ getElement().getStyle().setMarginLeft(0, Unit.PX);
+ mouseMoveStarted = true;
+ }
+
+ HorizontalPanel parent = (HorizontalPanel) getParent()
+ .getParent();
+ int relativeX = event.getRelativeX(parent.getElement())
+ - weekGrid.timebar.getOffsetWidth();
+ int halfHourDiff = 0;
+ if (moveY > 0) {
+ halfHourDiff = (startYrelative + moveY) / slotHeight;
+ } else {
+ halfHourDiff = (moveY - startYrelative) / slotHeight;
+ }
+
+ int dateCellWidth = getDateCellWidth();
+ long dayDiff = 0;
+ if (moveX >= 0) {
+ dayDiff = (startXrelative + moveX) / dateCellWidth;
+ } else {
+ dayDiff = (moveX - (dateCellWidth - startXrelative))
+ / dateCellWidth;
+ }
+
+ int dayOffset = relativeX / dateCellWidth;
+
+ // sanity check for right side overflow
+ int dateCellCount = weekGrid.getDateCellCount();
+ if (dayOffset >= dateCellCount) {
+ dayOffset--;
+ dayDiff--;
+ }
+
+ int dayOffsetPx = calculateDateCellOffsetPx(dayOffset)
+ + weekGrid.timebar.getOffsetWidth();
+
+ GWT.log("DateCellWidth: " + dateCellWidth + " dayDiff: "
+ + dayDiff + " dayOffset: " + dayOffset
+ + " dayOffsetPx: " + dayOffsetPx + " startXrelative: "
+ + startXrelative + " moveX: " + moveX);
+
+ if (relativeX < 0 || relativeX >= getDatesWidth()) {
+ return;
+ }
+
+ Style s = getElement().getStyle();
+
+ Date from = calendarEvent.getStartTime();
+ Date to = calendarEvent.getEndTime();
+ long duration = to.getTime() - from.getTime();
+
+ if (!clickTargetsResize()
+ && weekGrid.getCalendar().isEventMoveAllowed()) {
+ long daysMs = dayDiff * DateConstants.DAYINMILLIS;
+ from.setTime(startDatetimeFrom.getTime() + daysMs);
+ from.setTime(from.getTime()
+ + ((long) halfHourInMilliSeconds * halfHourDiff));
+ to.setTime((from.getTime() + duration));
+
+ calendarEvent.setStartTime(from);
+ calendarEvent.setEndTime(to);
+ calendarEvent.setStart(new Date(from.getTime()));
+ calendarEvent.setEnd(new Date(to.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (from.getHours() * 60)
+ + from.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+ startFromMinutes = calculateStartFromMinute(
+ startFromMinutes, from, to, dayOffsetPx);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+ updatePosition(startFromMinutes, range);
+
+ s.setLeft(dayOffsetPx, Unit.PX);
+
+ if (weekGrid.getDateCellWidths() != null) {
+ s.setWidth(weekGrid.getDateCellWidths()[dayOffset],
+ Unit.PX);
+ } else {
+ setWidth(moveWidth);
+ }
+
+ } else if (clickTarget == topResizeBar) {
+ long oldStartTime = startDatetimeFrom.getTime();
+ long newStartTime = oldStartTime
+ + ((long) halfHourInMilliSeconds * halfHourDiff);
+
+ if (!isTimeRangeTooSmall(newStartTime,
+ startDatetimeTo.getTime())) {
+ newStartTime = startDatetimeTo.getTime()
+ - getMinTimeRange();
+ }
+
+ from.setTime(newStartTime);
+
+ calendarEvent.setStartTime(from);
+ calendarEvent.setStart(new Date(from.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (from.getHours() * 60)
+ + from.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+
+ updatePosition(startFromMinutes, range);
+
+ } else if (clickTarget == bottomResizeBar) {
+ long oldEndTime = startDatetimeTo.getTime();
+ long newEndTime = oldEndTime
+ + ((long) halfHourInMilliSeconds * halfHourDiff);
+
+ if (!isTimeRangeTooSmall(startDatetimeFrom.getTime(),
+ newEndTime)) {
+ newEndTime = startDatetimeFrom.getTime()
+ + getMinTimeRange();
+ }
+
+ to.setTime(newEndTime);
+
+ calendarEvent.setEndTime(to);
+ calendarEvent.setEnd(new Date(to.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (startDatetimeFrom.getHours() * 60)
+ + startDatetimeFrom.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+ startFromMinutes = calculateStartFromMinute(
+ startFromMinutes, from, to, dayOffsetPx);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+ updatePosition(startFromMinutes, range);
+ }
+ }
+
+ private void cancelMouseMove() {
+ mouseMoveCanceled = true;
+
+ // reset and remove everything related to the event handling
+ Event.releaseCapture(getElement());
+ setFocus(false);
+
+ if (moveRegistration != null) {
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ }
+
+ mouseMoveStarted = false;
+ removeGlobalResizeStyle();
+
+ Style s = getElement().getStyle();
+ s.setZIndex(1);
+
+ // reset the position of the event
+ int dateCellWidth = getDateCellWidth();
+ int dayOffset = startXrelative / dateCellWidth;
+ s.clearLeft();
+
+ calendarEvent.setStartTime(startDatetimeFrom);
+ calendarEvent.setEndTime(startDatetimeTo);
+
+ long startFromMinutes = (startDatetimeFrom.getHours() * 60)
+ + startDatetimeFrom.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+
+ startFromMinutes = calculateStartFromMinute(startFromMinutes,
+ startDatetimeFrom, startDatetimeTo, dayOffset);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+
+ updatePosition(startFromMinutes, range);
+
+ startY = -1;
+ startX = -1;
+
+ // to reset the event width
+ ((DateCell) getParent()).recalculateEventWidths();
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private long calculateStartFromMinute(long startFromMinutes,
+ Date from, Date to, int dayOffset) {
+ boolean eventStartAtDifferentDay = from.getDate() != to
+ .getDate();
+ if (eventStartAtDifferentDay) {
+ long minutesOnPrevDay = (getTargetDateByCurrentPosition(
+ dayOffset).getTime() - from.getTime())
+ / DateConstants.MINUTEINMILLIS;
+ startFromMinutes = -1 * minutesOnPrevDay;
+ }
+
+ return startFromMinutes;
+ }
+
+ /**
+ * @param dateOffset
+ * @return the amount of pixels the given date is from the left side
+ */
+ private int calculateDateCellOffsetPx(int dateOffset) {
+ int dateCellOffset = 0;
+ int[] dateWidths = weekGrid.getDateCellWidths();
+
+ if (dateWidths != null) {
+ for (int i = 0; i < dateOffset; i++) {
+ dateCellOffset += dateWidths[i] + 1;
+ }
+ } else {
+ dateCellOffset = dateOffset * weekGrid.getDateCellWidth();
+ }
+
+ return dateCellOffset;
+ }
+
+ /**
+ * Check if the given time range is too small for events
+ *
+ * @param start
+ * @param end
+ * @return
+ */
+ private boolean isTimeRangeTooSmall(long start, long end) {
+ return (end - start) >= getMinTimeRange();
+ }
+
+ /**
+ * @return the minimum amount of ms that an event must last when
+ * resized
+ */
+ private long getMinTimeRange() {
+ return DateConstants.MINUTEINMILLIS * 30;
+ }
+
+ /**
+ * Build the string for sending resize events to server
+ *
+ * @param event
+ * @return
+ */
+ private String buildResizeString(CalendarEvent event) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(event.getIndex());
+ buffer.append(",");
+ buffer.append(DateUtil.formatClientSideDate(event.getStart()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event
+ .getStartTime()));
+ buffer.append(",");
+ buffer.append(DateUtil.formatClientSideDate(event.getEnd()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event.getEndTime()));
+
+ return buffer.toString();
+ }
+
+ private Date getTargetDateByCurrentPosition(int left) {
+ DateCell newParent = (DateCell) weekGrid.content
+ .getWidget((left / getDateCellWidth()) + 1);
+ Date targetDate = newParent.getDate();
+ return targetDate;
+ }
+
+ private int getDateCellWidth() {
+ return weekGrid.getDateCellWidth();
+ }
+
+ /* Returns total width of all date cells. */
+ private int getDatesWidth() {
+ if (weekGrid.width == -1) {
+ // Undefined width. Needs to be calculated by the known cell
+ // widths.
+ int count = weekGrid.content.getWidgetCount() - 1;
+ return count * getDateCellWidth();
+ }
+
+ return weekGrid.getInternalWidth();
+ }
+
+ /**
+ * @return true if the current mouse movement is resizing
+ */
+ private boolean clickTargetsResize() {
+ return weekGrid.getCalendar().isEventResizeAllowed()
+ && (clickTarget == topResizeBar || clickTarget == bottomResizeBar);
+ }
+
+ private void addGlobalResizeStyle() {
+ if (clickTarget == topResizeBar) {
+ weekGrid.getCalendar().addStyleDependentName("nresize");
+ } else if (clickTarget == bottomResizeBar) {
+ weekGrid.getCalendar().addStyleDependentName("sresize");
+ }
+ }
+
+ private void removeGlobalResizeStyle() {
+ weekGrid.getCalendar().removeStyleDependentName("nresize");
+ weekGrid.getCalendar().removeStyleDependentName("sresize");
+ }
+
+ public void setCalendarEvent(CalendarEvent calendarEvent) {
+ this.calendarEvent = calendarEvent;
+ }
+
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public void onContextMenu(ContextMenuEvent event) {
+ if (this.dateCell.weekgrid.getCalendar().getMouseEventListener() != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.dateCell.weekgrid.getCalendar().getMouseEventListener()
+ .contextMenu(event, this);
+ }
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ return eventIndex;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java
new file mode 100644
index 0000000000..d2add53389
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class DateCellGroup {
+ private WeekGridMinuteTimeRange range;
+ private final List<Integer> items;
+
+ public DateCellGroup(Integer index) {
+ items = new ArrayList<Integer>();
+ items.add(index);
+ }
+
+ public WeekGridMinuteTimeRange getDateRange() {
+ return range;
+ }
+
+ public Date getStart() {
+ return range.getStart();
+ }
+
+ public Date getEnd() {
+ return range.getEnd();
+ }
+
+ public void setDateRange(WeekGridMinuteTimeRange range) {
+ this.range = range;
+ }
+
+ public List<Integer> getItems() {
+ return items;
+ }
+
+ public void add(Integer index) {
+ items.add(index);
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java
new file mode 100644
index 0000000000..84726327e2
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Utility class for {@link Date} operations
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class DateUtil {
+
+ /**
+ * Checks if dates are same day without checking datetimes.
+ *
+ * @param date1
+ * @param date2
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean compareDate(Date date1, Date date2) {
+ if (date1.getDate() == date2.getDate()
+ && date1.getYear() == date2.getYear()
+ && date1.getMonth() == date2.getMonth()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param date
+ * the date to format
+ *
+ * @return given Date as String, for communicating to server-side
+ */
+ public static String formatClientSideDate(Date date) {
+ DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat(DateConstants.CLIENT_DATE_FORMAT);
+ return dateformat_date.format(date);
+ }
+
+ /**
+ * @param date
+ * the date to format
+ * @return given Date as String, for communicating to server-side
+ */
+ public static String formatClientSideTime(Date date) {
+ DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat(DateConstants.CLIENT_TIME_FORMAT);
+ return dateformat_date.format(date);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
new file mode 100644
index 0000000000..bb0155d892
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class DayToolbar extends HorizontalPanel implements ClickHandler {
+ private int width = 0;
+ protected static final int MARGINLEFT = 50;
+ protected static final int MARGINRIGHT = 20;
+ protected Button backLabel;
+ protected Button nextLabel;
+ private boolean verticalSized;
+ private boolean horizontalSized;
+ private VCalendar calendar;
+
+ public DayToolbar(VCalendar vcalendar) {
+ calendar = vcalendar;
+
+ setStylePrimaryName("v-calendar-header-week");
+ backLabel = new Button();
+ backLabel.setStylePrimaryName("v-calendar-back");
+ nextLabel = new Button();
+ nextLabel.addClickHandler(this);
+ nextLabel.setStylePrimaryName("v-calendar-next");
+ backLabel.addClickHandler(this);
+ setBorderWidth(0);
+ setSpacing(0);
+ }
+
+ public void setWidthPX(int width) {
+ this.width = (width - MARGINLEFT) - MARGINRIGHT;
+ // super.setWidth(this.width + "px");
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ updateCellWidths();
+ }
+
+ public void updateCellWidths() {
+ int count = getWidgetCount();
+ if (count > 0) {
+ setCellWidth(backLabel, MARGINLEFT + "px");
+ setCellWidth(nextLabel, MARGINRIGHT + "px");
+ setCellHorizontalAlignment(nextLabel, ALIGN_RIGHT);
+ int cellw = width / (count - 2);
+ int remain = width % (count - 2);
+ int cellw2 = cellw + 1;
+ if (cellw > 0) {
+ int[] cellWidths = VCalendar
+ .distributeSize(width, count - 2, 0);
+ for (int i = 1; i < count - 1; i++) {
+ Widget widget = getWidget(i);
+ // if (remain > 0) {
+ // setCellWidth(widget, cellw2 + "px");
+ // remain--;
+ // } else {
+ // setCellWidth(widget, cellw + "px");
+ // }
+ setCellWidth(widget, cellWidths[i - 1] + "px");
+ widget.setWidth(cellWidths[i - 1] + "px");
+ }
+ }
+ }
+ }
+
+ public void add(String dayName, final String date,
+ String localized_date_format, String extraClass) {
+ Label l = new Label(dayName + " " + localized_date_format);
+ l.setStylePrimaryName("v-calendar-header-day");
+
+ if (extraClass != null) {
+ l.addStyleDependentName(extraClass);
+ }
+
+ if (verticalSized) {
+ l.addStyleDependentName("Vsized");
+ }
+ if (horizontalSized) {
+ l.addStyleDependentName("Hsized");
+ }
+
+ l.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ if (calendar.getDateClickListener() != null) {
+ calendar.getDateClickListener().dateClick(date);
+ }
+ }
+ });
+
+ add(l);
+ }
+
+ public void addBackButton() {
+ if (!calendar.isBackwardNavigationEnabled()) {
+ nextLabel.getElement().getStyle().setHeight(0, Unit.PX);
+ }
+ add(backLabel);
+ }
+
+ public void addNextButton() {
+ if (!calendar.isForwardNavigationEnabled()) {
+ backLabel.getElement().getStyle().setHeight(0, Unit.PX);
+ }
+ add(nextLabel);
+ }
+
+ public void onClick(ClickEvent event) {
+ if (!calendar.isDisabledOrReadOnly()) {
+ if (event.getSource() == nextLabel) {
+ if (calendar.getForwardListener() != null) {
+ calendar.getForwardListener().forward();
+ }
+ } else if (event.getSource() == backLabel) {
+ if (calendar.getBackwardListener() != null) {
+ calendar.getBackwardListener().backward();
+ }
+ }
+ }
+ }
+
+ public void setVerticalSized(boolean sized) {
+ verticalSized = sized;
+ updateDayLabelSizedStyleNames();
+ }
+
+ public void setHorizontalSized(boolean sized) {
+ horizontalSized = sized;
+ updateDayLabelSizedStyleNames();
+ }
+
+ private void updateDayLabelSizedStyleNames() {
+ Iterator<Widget> it = iterator();
+ while (it.hasNext()) {
+ updateWidgetSizedStyleName(it.next());
+ }
+ }
+
+ private void updateWidgetSizedStyleName(Widget w) {
+ if (verticalSized) {
+ w.addStyleDependentName("Vsized");
+ } else {
+ w.removeStyleDependentName("VSized");
+ }
+ if (horizontalSized) {
+ w.addStyleDependentName("Hsized");
+ } else {
+ w.removeStyleDependentName("HSized");
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java
new file mode 100644
index 0000000000..62332385d2
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A ComplexPanel that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableComplexPanel extends ComplexPanel implements
+ HasFocusHandlers, HasBlurHandlers, HasKeyDownHandlers,
+ HasKeyPressHandlers, Focusable {
+
+ protected void makeFocusable() {
+ // make focusable, as we don't need access key magic we don't need to
+ // use FocusImpl.createFocusable
+ getElement().setTabIndex(0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.
+ * google.gwt.event.dom.client.FocusHandler)
+ */
+ public HandlerRegistration addFocusHandler(FocusHandler handler) {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google
+ * .gwt.event.dom.client.BlurHandler)
+ */
+ public HandlerRegistration addBlurHandler(BlurHandler handler) {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(
+ * com.google.gwt.event.dom.client.KeyDownHandler)
+ */
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler
+ * (com.google.gwt.event.dom.client.KeyPressHandler)
+ */
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+ return addDomHandler(handler, KeyPressEvent.getType());
+ }
+
+ /**
+ * Sets/Removes the keyboard focus to the panel.
+ *
+ * @param focus
+ * If set to true then the focus is moved to the panel, if set to
+ * false the focus is removed
+ */
+ public void setFocus(boolean focus) {
+ if (focus) {
+ FocusImpl.getFocusImplForPanel().focus(getElement());
+ } else {
+ FocusImpl.getFocusImplForPanel().blur(getElement());
+ }
+ }
+
+ /**
+ * Focus the panel
+ */
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java
new file mode 100644
index 0000000000..d3177362bf
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A Grid that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableGrid extends Grid implements HasFocusHandlers,
+ HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+ /**
+ * Constructor
+ */
+ public FocusableGrid() {
+ super();
+ makeFocusable();
+ }
+
+ public FocusableGrid(int rows, int columns) {
+ super(rows, columns);
+ makeFocusable();
+ }
+
+ protected void makeFocusable() {
+ // make focusable, as we don't need access key magic we don't need to
+ // use FocusImpl.createFocusable
+ getElement().setTabIndex(0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.
+ * google.gwt.event.dom.client.FocusHandler)
+ */
+ public HandlerRegistration addFocusHandler(FocusHandler handler) {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google
+ * .gwt.event.dom.client.BlurHandler)
+ */
+ public HandlerRegistration addBlurHandler(BlurHandler handler) {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(
+ * com.google.gwt.event.dom.client.KeyDownHandler)
+ */
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler
+ * (com.google.gwt.event.dom.client.KeyPressHandler)
+ */
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+ return addDomHandler(handler, KeyPressEvent.getType());
+ }
+
+ /**
+ * Sets/Removes the keyboard focus to the panel.
+ *
+ * @param focus
+ * If set to true then the focus is moved to the panel, if set to
+ * false the focus is removed
+ */
+ public void setFocus(boolean focus) {
+ if (focus) {
+ FocusImpl.getFocusImplForPanel().focus(getElement());
+ } else {
+ FocusImpl.getFocusImplForPanel().blur(getElement());
+ }
+ }
+
+ /**
+ * Focus the panel
+ */
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java
new file mode 100644
index 0000000000..c3fe1958f0
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A HTML widget that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableHTML extends HTML implements HasFocusHandlers,
+ HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+ /**
+ * Constructor
+ */
+ public FocusableHTML() {
+ // make focusable, as we don't need access key magic we don't need to
+ // use FocusImpl.createFocusable
+ getElement().setTabIndex(0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.
+ * google.gwt.event.dom.client.FocusHandler)
+ */
+ public HandlerRegistration addFocusHandler(FocusHandler handler) {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google
+ * .gwt.event.dom.client.BlurHandler)
+ */
+ public HandlerRegistration addBlurHandler(BlurHandler handler) {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(
+ * com.google.gwt.event.dom.client.KeyDownHandler)
+ */
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler
+ * (com.google.gwt.event.dom.client.KeyPressHandler)
+ */
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+ return addDomHandler(handler, KeyPressEvent.getType());
+ }
+
+ /**
+ * Sets/Removes the keyboard focus to the panel.
+ *
+ * @param focus
+ * If set to true then the focus is moved to the panel, if set to
+ * false the focus is removed
+ */
+ public void setFocus(boolean focus) {
+ if (focus) {
+ FocusImpl.getFocusImplForPanel().focus(getElement());
+ } else {
+ FocusImpl.getFocusImplForPanel().blur(getElement());
+ }
+ }
+
+ /**
+ * Focus the panel
+ */
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java b/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java
new file mode 100644
index 0000000000..5827068840
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+/**
+ * For Calendar client-side internal use only.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public interface HasTooltipKey {
+ /**
+ * Gets the key associated for the Widget implementing this interface. This
+ * key is used for getting a tooltip title identified by the key
+ *
+ * @return the tooltip key
+ */
+ Object getTooltipKey();
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java
new file mode 100644
index 0000000000..b7f6ee7a3c
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * The label in a month cell
+ *
+ * @since 7.1
+ */
+public class MonthEventLabel extends HTML implements HasTooltipKey {
+
+ private static final String STYLENAME = "v-calendar-event";
+
+ private boolean timeSpecificEvent = false;
+ private Integer eventIndex;
+ private VCalendar calendar;
+ private String caption;
+ private Date time;
+
+ /**
+ * Default constructor
+ */
+ public MonthEventLabel() {
+ setStylePrimaryName(STYLENAME);
+ }
+
+ /**
+ * Set the time of the event label
+ *
+ * @param date
+ * The date object that specifies the time
+ */
+ public void setTime(Date date) {
+ time = date;
+ renderCaption();
+ }
+
+ /**
+ * Set the caption of the event label
+ *
+ * @param caption
+ * The caption string, can be HTML
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ renderCaption();
+ }
+
+ /**
+ * Renders the caption in the DIV element
+ */
+ private void renderCaption() {
+ StringBuilder html = new StringBuilder();
+ if (caption != null && time != null) {
+ html.append("<span class=\"" + STYLENAME + "-time\">");
+ html.append(calendar.getTimeFormat().format(time));
+ html.append("</span> ");
+ html.append(caption);
+ } else if (caption != null) {
+ html.append(caption);
+ } else if (time != null) {
+ html.append("<span class=\"" + STYLENAME + "-time\">");
+ html.append(calendar.getTimeFormat().format(time));
+ html.append("</span>");
+ }
+ super.setHTML(html.toString());
+ }
+
+ /**
+ * Set the (server side) index of the event
+ *
+ * @param index
+ * The integer index
+ */
+ public void setEventIndex(int index) {
+ eventIndex = index;
+ }
+
+ /**
+ * Set the Calendar instance this label belongs to
+ *
+ * @param calendar
+ * The calendar instance
+ */
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ /**
+ * Is the event bound to a specific time
+ *
+ * @return
+ */
+ public boolean isTimeSpecificEvent() {
+ return timeSpecificEvent;
+ }
+
+ /**
+ * Is the event bound to a specific time
+ *
+ * @param timeSpecificEvent
+ * True if the event is bound to a time, false if it is only
+ * bound to the day
+ */
+ public void setTimeSpecificEvent(boolean timeSpecificEvent) {
+ this.timeSpecificEvent = timeSpecificEvent;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.HTML#setHTML(java.lang.String)
+ */
+ @Override
+ public void setHTML(String html) {
+ throw new UnsupportedOperationException(
+ "Use setCaption() and setTime() instead");
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ return eventIndex;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
new file mode 100644
index 0000000000..f5afd12e42
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class MonthGrid extends FocusableGrid implements KeyDownHandler {
+
+ private SimpleDayCell selectionStart;
+ private SimpleDayCell selectionEnd;
+ private final VCalendar calendar;
+ private boolean rangeSelectDisabled;
+ private boolean disabled;
+ private boolean enabled = true;
+ private final HandlerRegistration keyDownHandler;
+
+ public MonthGrid(VCalendar parent, int rows, int columns) {
+ super(rows, columns);
+ calendar = parent;
+ setCellSpacing(0);
+ setCellPadding(0);
+ setStylePrimaryName("v-calendar-month");
+
+ keyDownHandler = addKeyDownHandler(this);
+ }
+
+ @Override
+ protected void onUnload() {
+ keyDownHandler.removeHandler();
+ super.onUnload();
+ }
+
+ public void setSelectionEnd(SimpleDayCell simpleDayCell) {
+ selectionEnd = simpleDayCell;
+ updateSelection();
+ }
+
+ public void setSelectionStart(SimpleDayCell simpleDayCell) {
+ if (!rangeSelectDisabled && isEnabled()) {
+ selectionStart = simpleDayCell;
+ setFocus(true);
+ }
+
+ }
+
+ private void updateSelection() {
+ if (selectionStart == null) {
+ return;
+ }
+ if (selectionStart != null && selectionEnd != null) {
+ Date startDate = selectionStart.getDate();
+ Date endDate = selectionEnd.getDate();
+ for (int row = 0; row < getRowCount(); row++) {
+ for (int cell = 0; cell < getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(row, cell);
+ if (sdc == null) {
+ return;
+ }
+ Date d = sdc.getDate();
+ if (startDate.compareTo(d) <= 0
+ && endDate.compareTo(d) >= 0) {
+ sdc.addStyleDependentName("selected");
+ } else if (startDate.compareTo(d) >= 0
+ && endDate.compareTo(d) <= 0) {
+ sdc.addStyleDependentName("selected");
+ } else {
+ sdc.removeStyleDependentName("selected");
+ }
+ }
+ }
+ }
+ }
+
+ public void setSelectionReady() {
+ if (selectionStart != null && selectionEnd != null) {
+ String value = "";
+ Date startDate = selectionStart.getDate();
+ Date endDate = selectionEnd.getDate();
+ if (startDate.compareTo(endDate) > 0) {
+ Date temp = startDate;
+ startDate = endDate;
+ endDate = temp;
+ }
+
+ if (calendar.getRangeSelectListener() != null) {
+ value = calendar.getDateFormat().format(startDate) + "TO"
+ + calendar.getDateFormat().format(endDate);
+ calendar.getRangeSelectListener().rangeSelected(value);
+ }
+ selectionStart = null;
+ selectionEnd = null;
+ setFocus(false);
+ }
+ }
+
+ public void cancelRangeSelection() {
+ if (selectionStart != null && selectionEnd != null) {
+ for (int row = 0; row < getRowCount(); row++) {
+ for (int cell = 0; cell < getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(row, cell);
+ if (sdc == null) {
+ return;
+ }
+ sdc.removeStyleDependentName("selected");
+ }
+ }
+ }
+ setFocus(false);
+ selectionStart = null;
+ }
+
+ public void updateCellSizes(int totalWidthPX, int totalHeightPX) {
+ boolean setHeight = totalHeightPX > 0;
+ boolean setWidth = totalWidthPX > 0;
+ int rows = getRowCount();
+ int cells = getCellCount(0);
+ int cellWidth = (totalWidthPX / cells) - 1;
+ int widthRemainder = totalWidthPX % cells;
+ // Division for cells might not be even. Distribute it evenly to
+ // will whole space.
+ int heightPX = totalHeightPX;
+ int cellHeight = heightPX / rows;
+ int heightRemainder = heightPX % rows;
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cells; j++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(i, j);
+
+ if (setWidth) {
+ if (widthRemainder > 0) {
+ sdc.setWidth(cellWidth + 1 + "px");
+ widthRemainder--;
+
+ } else {
+ sdc.setWidth(cellWidth + "px");
+ }
+ }
+
+ if (setHeight) {
+ if (heightRemainder > 0) {
+ sdc.setHeightPX(cellHeight + 1, true);
+
+ } else {
+ sdc.setHeightPX(cellHeight, true);
+ }
+ } else {
+ sdc.setHeightPX(-1, true);
+ }
+ }
+ heightRemainder--;
+ }
+ }
+
+ /**
+ * Disable or enable possibility to select ranges
+ */
+ public void setRangeSelect(boolean b) {
+ rangeSelectDisabled = !b;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeKeyCode();
+ if (KeyCodes.KEY_ESCAPE == keycode && selectionStart != null) {
+ cancelRangeSelection();
+ }
+ }
+
+ public int getDayCellIndex(SimpleDayCell dayCell) {
+ int rows = getRowCount();
+ int cells = getCellCount(0);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cells; j++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(i, j);
+ if (dayCell == sdc) {
+ return i * cells + j;
+ }
+ }
+ }
+
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
new file mode 100644
index 0000000000..8d1ca0fcda
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
@@ -0,0 +1,696 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.FocusableFlowPanel;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * A class representing a single cell within the calendar in month-view
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class SimpleDayCell extends FocusableFlowPanel implements
+ MouseUpHandler, MouseDownHandler, MouseOverHandler, MouseMoveHandler {
+
+ private static int BOTTOMSPACERHEIGHT = -1;
+ private static int EVENTHEIGHT = -1;
+ private static final int BORDERPADDINGSIZE = 1;
+
+ private final VCalendar calendar;
+ private Date date;
+ private int intHeight;
+ private final HTML bottomspacer;
+ private final Label caption;
+ private final CalendarEvent[] events = new CalendarEvent[10];
+ private final int cell;
+ private final int row;
+ private boolean monthNameVisible;
+ private HandlerRegistration mouseUpRegistration;
+ private HandlerRegistration mouseDownRegistration;
+ private HandlerRegistration mouseOverRegistration;
+ private boolean monthEventMouseDown;
+ private boolean labelMouseDown;
+ private int eventCount = 0;
+
+ private int startX = -1;
+ private int startY = -1;
+ private int startYrelative;
+ private int startXrelative;
+ private Date startDateFrom;
+ private Date startDateTo;
+ private int prevDayDiff = 0;
+ private int prevWeekDiff = 0;
+ private HandlerRegistration moveRegistration;
+ private CalendarEvent moveEvent;
+ private Widget clickedWidget;
+ private HandlerRegistration bottomSpacerMouseDownHandler;
+ private boolean scrollable = false;
+ private boolean eventCanceled;
+ private MonthGrid monthGrid;
+ private HandlerRegistration keyDownHandler;
+
+ public SimpleDayCell(VCalendar calendar, int row, int cell) {
+ this.calendar = calendar;
+ this.row = row;
+ this.cell = cell;
+ setStylePrimaryName("v-calendar-month-day");
+ caption = new Label();
+ bottomspacer = new HTML();
+ bottomspacer.setStyleName("v-calendar-bottom-spacer-empty");
+ bottomspacer.setWidth(3 + "em");
+ caption.setStyleName("v-calendar-day-number");
+ add(caption);
+ add(bottomspacer);
+ caption.addMouseDownHandler(this);
+ caption.addMouseUpHandler(this);
+ }
+
+ @Override
+ public void onLoad() {
+ BOTTOMSPACERHEIGHT = bottomspacer.getOffsetHeight();
+ EVENTHEIGHT = BOTTOMSPACERHEIGHT;
+ }
+
+ public void setMonthGrid(MonthGrid monthGrid) {
+ this.monthGrid = monthGrid;
+ }
+
+ public MonthGrid getMonthGrid() {
+ return monthGrid;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setDate(Date date) {
+ int dateOfMonth = date.getDate();
+ if (monthNameVisible) {
+ caption.setText(dateOfMonth + " "
+ + calendar.getMonthNames()[date.getMonth()]);
+ } else {
+ caption.setText("" + dateOfMonth);
+ }
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void reDraw(boolean clear) {
+ setHeightPX(intHeight + BORDERPADDINGSIZE, clear);
+ }
+
+ /*
+ * Events and whole cell content are drawn by this method. By the
+ * clear-argument, you can choose to clear all old content. Notice that
+ * clearing will also remove all element's event handlers.
+ */
+ public void setHeightPX(int px, boolean clear) {
+ // measure from DOM if needed
+ if (px < 0) {
+ intHeight = getOffsetHeight() - BORDERPADDINGSIZE;
+ } else {
+ intHeight = px - BORDERPADDINGSIZE;
+ }
+
+ // Couldn't measure height or it ended up negative. Don't bother
+ // continuing
+ if (intHeight == -1) {
+ return;
+ }
+
+ if (clear) {
+ while (getWidgetCount() > 1) {
+ remove(1);
+ }
+ }
+
+ // How many events can be shown in UI
+ int slots = 0;
+ if (scrollable) {
+ for (int i = 0; i < events.length; i++) {
+ if (events[i] != null) {
+ slots = i + 1;
+ }
+ }
+ setHeight(intHeight + "px"); // Fixed height
+ } else {
+ // Dynamic height by the content
+ DOM.removeElementAttribute(getElement(), "height");
+ slots = (intHeight - caption.getOffsetHeight() - BOTTOMSPACERHEIGHT)
+ / EVENTHEIGHT;
+ if (slots > 10) {
+ slots = 10;
+ }
+ }
+
+ updateEvents(slots, clear);
+
+ }
+
+ public void updateEvents(int slots, boolean clear) {
+ int eventsAdded = 0;
+
+ for (int i = 0; i < slots; i++) {
+ CalendarEvent e = events[i];
+ if (e == null) {
+ // Empty slot
+ HTML slot = new HTML();
+ slot.setStyleName("v-calendar-spacer");
+ if (!clear) {
+ remove(i + 1);
+ insert(slot, i + 1);
+ } else {
+ add(slot);
+ }
+ } else {
+ // Event slot
+ eventsAdded++;
+ if (!clear) {
+ Widget w = getWidget(i + 1);
+ if (!(w instanceof MonthEventLabel)) {
+ remove(i + 1);
+ insert(createMonthEventLabel(e), i + 1);
+ }
+ } else {
+ add(createMonthEventLabel(e));
+ }
+ }
+ }
+
+ int remainingSpace = intHeight
+ - ((slots * EVENTHEIGHT) + BOTTOMSPACERHEIGHT + caption
+ .getOffsetHeight());
+ int newHeight = remainingSpace + BOTTOMSPACERHEIGHT;
+ if (newHeight < 0) {
+ newHeight = EVENTHEIGHT;
+ }
+ bottomspacer.setHeight(newHeight + "px");
+
+ if (clear) {
+ add(bottomspacer);
+ }
+
+ int more = eventCount - eventsAdded;
+ if (more > 0) {
+ if (bottomSpacerMouseDownHandler == null) {
+ bottomSpacerMouseDownHandler = bottomspacer
+ .addMouseDownHandler(this);
+ }
+ bottomspacer.setStyleName("v-calendar-bottom-spacer");
+ bottomspacer.setText("+ " + more);
+ } else {
+ if (!scrollable && bottomSpacerMouseDownHandler != null) {
+ bottomSpacerMouseDownHandler.removeHandler();
+ bottomSpacerMouseDownHandler = null;
+ }
+
+ if (scrollable) {
+ bottomspacer.setText("[ - ]");
+ } else {
+ bottomspacer.setStyleName("v-calendar-bottom-spacer-empty");
+ bottomspacer.setText("");
+ }
+ }
+ }
+
+ private MonthEventLabel createMonthEventLabel(CalendarEvent e) {
+ long rangeInMillis = e.getRangeInMilliseconds();
+ boolean timeEvent = rangeInMillis <= DateConstants.DAYINMILLIS
+ && !e.isAllDay();
+ Date fromDatetime = e.getStartTime();
+
+ // Create a new MonthEventLabel
+ MonthEventLabel eventDiv = new MonthEventLabel();
+ eventDiv.addStyleDependentName("month");
+ eventDiv.addMouseDownHandler(this);
+ eventDiv.addMouseUpHandler(this);
+ eventDiv.setCalendar(calendar);
+ eventDiv.setEventIndex(e.getIndex());
+
+ if (timeEvent) {
+ eventDiv.setTimeSpecificEvent(true);
+ if (e.getStyleName() != null) {
+ eventDiv.addStyleDependentName(e.getStyleName());
+ }
+ eventDiv.setCaption(e.getCaption());
+ eventDiv.setTime(fromDatetime);
+
+ } else {
+ eventDiv.setTimeSpecificEvent(false);
+ Date from = e.getStart();
+ Date to = e.getEnd();
+ if (e.getStyleName().length() > 0) {
+ eventDiv.addStyleName("month-event " + e.getStyleName());
+ } else {
+ eventDiv.addStyleName("month-event");
+ }
+ int fromCompareToDate = from.compareTo(date);
+ int toCompareToDate = to.compareTo(date);
+ eventDiv.addStyleDependentName("all-day");
+ if (fromCompareToDate == 0) {
+ eventDiv.addStyleDependentName("start");
+ eventDiv.setCaption(e.getCaption());
+
+ } else if (fromCompareToDate < 0 && cell == 0) {
+ eventDiv.addStyleDependentName("continued-from");
+ eventDiv.setCaption(e.getCaption());
+ }
+ if (toCompareToDate == 0) {
+ eventDiv.addStyleDependentName("end");
+ } else if (toCompareToDate > 0
+ && (cell + 1) == getMonthGrid().getCellCount(row)) {
+ eventDiv.addStyleDependentName("continued-to");
+ }
+ if (e.getStyleName() != null) {
+ eventDiv.addStyleDependentName(e.getStyleName() + "-all-day");
+ }
+ }
+
+ return eventDiv;
+ }
+
+ private void setUnlimitedCellHeight() {
+ scrollable = true;
+ addStyleDependentName("scrollable");
+ }
+
+ private void setLimitedCellHeight() {
+ scrollable = false;
+ removeStyleDependentName("scrollable");
+ }
+
+ public void addCalendarEvent(CalendarEvent e) {
+ eventCount++;
+ int slot = e.getSlotIndex();
+ if (slot == -1) {
+ for (int i = 0; i < events.length; i++) {
+ if (events[i] == null) {
+ events[i] = e;
+ e.setSlotIndex(i);
+ break;
+ }
+ }
+ } else {
+ events[slot] = e;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setMonthNameVisible(boolean b) {
+ monthNameVisible = b;
+ int dateOfMonth = date.getDate();
+ caption.setText(dateOfMonth + " "
+ + calendar.getMonthNames()[date.getMonth()]);
+ }
+
+ public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
+ return addDomHandler(handler, MouseMoveEvent.getType());
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ mouseUpRegistration = addDomHandler(this, MouseUpEvent.getType());
+ mouseDownRegistration = addDomHandler(this, MouseDownEvent.getType());
+ mouseOverRegistration = addDomHandler(this, MouseOverEvent.getType());
+ }
+
+ @Override
+ protected void onDetach() {
+ mouseUpRegistration.removeHandler();
+ mouseDownRegistration.removeHandler();
+ mouseOverRegistration.removeHandler();
+ super.onDetach();
+ }
+
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ Widget w = (Widget) event.getSource();
+ if (moveRegistration != null) {
+ Event.releaseCapture(getElement());
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ keyDownHandler.removeHandler();
+ keyDownHandler = null;
+ }
+
+ if (w == bottomspacer && monthEventMouseDown) {
+ GWT.log("Mouse up over bottomspacer");
+
+ } else if (clickedWidget instanceof MonthEventLabel
+ && monthEventMouseDown) {
+ MonthEventLabel mel = (MonthEventLabel) clickedWidget;
+
+ int endX = event.getClientX();
+ int endY = event.getClientY();
+ int xDiff = startX - endX;
+ int yDiff = startY - endY;
+ startX = -1;
+ startY = -1;
+ prevDayDiff = 0;
+ prevWeekDiff = 0;
+
+ if (!mel.isTimeSpecificEvent()
+ && (xDiff < -3 || xDiff > 3 || yDiff < -3 || yDiff > 3)) {
+ eventMoved(moveEvent);
+
+ } else if (calendar.getEventClickListener() != null) {
+ CalendarEvent e = getEventByWidget(mel);
+ calendar.getEventClickListener().eventClick(e);
+ }
+
+ moveEvent = null;
+ } else if (w == this) {
+ getMonthGrid().setSelectionReady();
+
+ } else if (w instanceof Label && labelMouseDown) {
+ String clickedDate = calendar.getDateFormat().format(date);
+ if (calendar.getDateClickListener() != null) {
+ calendar.getDateClickListener().dateClick(clickedDate);
+ }
+ }
+ monthEventMouseDown = false;
+ labelMouseDown = false;
+ clickedWidget = null;
+ }
+
+ public void onMouseDown(MouseDownEvent event) {
+ if (calendar.isDisabled()
+ || event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ Widget w = (Widget) event.getSource();
+ clickedWidget = w;
+
+ if (w instanceof MonthEventLabel) {
+ // event clicks should be allowed even when read-only
+ monthEventMouseDown = true;
+
+ if (w instanceof MonthEventLabel) {
+ startCalendarEventDrag(event, (MonthEventLabel) w);
+ }
+ } else if (!calendar.isReadOnly()) {
+ // these are not allowed when in read-only
+ if (w == bottomspacer) {
+ if (scrollable) {
+ setLimitedCellHeight();
+ } else {
+ setUnlimitedCellHeight();
+ }
+ reDraw(true);
+
+ } else if (w == this && !scrollable) {
+ MonthGrid grid = getMonthGrid();
+ if (grid.isEnabled() && calendar.isRangeSelectAllowed()) {
+ grid.setSelectionStart(this);
+ grid.setSelectionEnd(this);
+ }
+ } else if (w instanceof Label) {
+ labelMouseDown = true;
+ }
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ public void onMouseOver(MouseOverEvent event) {
+ event.preventDefault();
+ getMonthGrid().setSelectionEnd(this);
+ }
+
+ public void onMouseMove(MouseMoveEvent event) {
+ if (clickedWidget instanceof MonthEventLabel && !monthEventMouseDown
+ || (startY < 0 && startX < 0)) {
+ return;
+ }
+
+ MonthEventLabel w = (MonthEventLabel) clickedWidget;
+
+ if (calendar.isDisabledOrReadOnly()) {
+ Event.releaseCapture(getElement());
+ monthEventMouseDown = false;
+ startY = -1;
+ startX = -1;
+ return;
+ }
+
+ int currentY = event.getClientY();
+ int currentX = event.getClientX();
+ int moveY = (currentY - startY);
+ int moveX = (currentX - startX);
+ if ((moveY < 5 && moveY > -6) && (moveX < 5 && moveX > -6)) {
+ return;
+ }
+
+ int dateCellWidth = getWidth();
+ int dateCellHeigth = getHeigth();
+
+ Element parent = getMonthGrid().getElement();
+ int relativeX = event.getRelativeX(parent);
+ int relativeY = event.getRelativeY(parent);
+ int weekDiff = 0;
+ if (moveY > 0) {
+ weekDiff = (startYrelative + moveY) / dateCellHeigth;
+ } else {
+ weekDiff = (moveY - (dateCellHeigth - startYrelative))
+ / dateCellHeigth;
+ }
+
+ int dayDiff = 0;
+ if (moveX >= 0) {
+ dayDiff = (startXrelative + moveX) / dateCellWidth;
+ } else {
+ dayDiff = (moveX - (dateCellWidth - startXrelative))
+ / dateCellWidth;
+ }
+ // Check boundaries
+ if (relativeY < 0
+ || relativeY >= (calendar.getMonthGrid().getRowCount() * dateCellHeigth)
+ || relativeX < 0
+ || relativeX >= (calendar.getMonthGrid().getColumnCount() * dateCellWidth)) {
+ return;
+ }
+
+ GWT.log("Event moving delta: " + weekDiff + " weeks " + dayDiff
+ + " days" + " (" + getCell() + "," + getRow() + ")");
+
+ CalendarEvent e = moveEvent;
+ if (e == null) {
+ e = getEventByWidget(w);
+ }
+
+ Date from = e.getStart();
+ Date to = e.getEnd();
+ long duration = to.getTime() - from.getTime();
+
+ long daysMs = dayDiff * DateConstants.DAYINMILLIS;
+ long weeksMs = weekDiff * DateConstants.WEEKINMILLIS;
+ from.setTime(startDateFrom.getTime() + weeksMs + daysMs);
+ to.setTime((from.getTime() + duration));
+ e.setStart(from);
+ e.setEnd(to);
+ e.setStartTime(new Date(from.getTime()));
+ e.setEndTime(new Date(to.getTime()));
+
+ updateDragPosition(w, dayDiff, weekDiff);
+ }
+
+ private void eventMoved(CalendarEvent e) {
+ calendar.updateEventToMonthGrid(e);
+ if (calendar.getEventMovedListener() != null) {
+ calendar.getEventMovedListener().eventMoved(e);
+ }
+ }
+
+ public void startCalendarEventDrag(MouseDownEvent event,
+ final MonthEventLabel w) {
+ if (w.isTimeSpecificEvent()) {
+ return;
+ }
+
+ moveRegistration = addMouseMoveHandler(this);
+ startX = event.getClientX();
+ startY = event.getClientY();
+ startYrelative = event.getRelativeY(w.getParent().getElement())
+ % getHeigth();
+ startXrelative = event.getRelativeX(w.getParent().getElement())
+ % getWidth();
+
+ CalendarEvent e = getEventByWidget(w);
+ startDateFrom = (Date) e.getStart().clone();
+ startDateTo = (Date) e.getEnd().clone();
+
+ Event.setCapture(getElement());
+ keyDownHandler = addKeyDownHandler(new KeyDownHandler() {
+
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+ cancelEventDrag(w);
+ }
+ }
+
+ });
+
+ focus();
+
+ GWT.log("Start drag");
+ }
+
+ protected void cancelEventDrag(MonthEventLabel w) {
+ if (moveRegistration != null) {
+ // reset position
+ if (moveEvent == null) {
+ moveEvent = getEventByWidget(w);
+ }
+
+ moveEvent.setStart(startDateFrom);
+ moveEvent.setEnd(startDateTo);
+ calendar.updateEventToMonthGrid(moveEvent);
+
+ // reset drag-related properties
+ Event.releaseCapture(getElement());
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ keyDownHandler.removeHandler();
+ keyDownHandler = null;
+ setFocus(false);
+ monthEventMouseDown = false;
+ startY = -1;
+ startX = -1;
+ moveEvent = null;
+ labelMouseDown = false;
+ clickedWidget = null;
+ }
+ }
+
+ public void updateDragPosition(MonthEventLabel w, int dayDiff, int weekDiff) {
+ // Draw event to its new position only when position has changed
+ if (dayDiff == prevDayDiff && weekDiff == prevWeekDiff) {
+ return;
+ }
+
+ prevDayDiff = dayDiff;
+ prevWeekDiff = weekDiff;
+
+ if (moveEvent == null) {
+ moveEvent = getEventByWidget(w);
+ }
+
+ calendar.updateEventToMonthGrid(moveEvent);
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getCell() {
+ return cell;
+ }
+
+ public int getHeigth() {
+ return intHeight + BORDERPADDINGSIZE;
+ }
+
+ public int getWidth() {
+ return getOffsetWidth() - BORDERPADDINGSIZE;
+ }
+
+ public void setToday(boolean today) {
+ if (today) {
+ addStyleDependentName("today");
+ } else {
+ removeStyleDependentName("today");
+ }
+ }
+
+ public boolean removeEvent(CalendarEvent targetEvent,
+ boolean reDrawImmediately) {
+ int slot = targetEvent.getSlotIndex();
+ if (slot < 0) {
+ return false;
+ }
+
+ CalendarEvent e = getCalendarEvent(slot);
+ if (targetEvent.equals(e)) {
+ events[slot] = null;
+ eventCount--;
+ if (reDrawImmediately) {
+ reDraw(moveEvent == null);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private CalendarEvent getEventByWidget(MonthEventLabel eventWidget) {
+ int index = getWidgetIndex(eventWidget);
+ return getCalendarEvent(index - 1);
+ }
+
+ public CalendarEvent getCalendarEvent(int i) {
+ return events[i];
+ }
+
+ public CalendarEvent[] getEvents() {
+ return events;
+ }
+
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ public CalendarEvent getMoveEvent() {
+ return moveEvent;
+ }
+
+ public void addEmphasisStyle() {
+ addStyleDependentName("dragemphasis");
+ }
+
+ public void removeEmphasisStyle() {
+ removeStyleDependentName("dragemphasis");
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java
new file mode 100644
index 0000000000..fc75136b93
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+public class SimpleDayToolbar extends HorizontalPanel {
+ private int width = 0;
+ private boolean isWidthUndefined = false;
+
+ public SimpleDayToolbar() {
+ setStylePrimaryName("v-calendar-header-month");
+ }
+
+ public void setDayNames(String[] dayNames) {
+ clear();
+ for (int i = 0; i < dayNames.length; i++) {
+ Label l = new Label(dayNames[i]);
+ l.setStylePrimaryName("v-calendar-header-day");
+ add(l);
+ }
+ updateCellWidth();
+ }
+
+ public void setWidthPX(int width) {
+ this.width = width;
+
+ setWidthUndefined(width == -1);
+
+ if (!isWidthUndefined()) {
+ super.setWidth(this.width + "px");
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ }
+ updateCellWidth();
+ }
+
+ private boolean isWidthUndefined() {
+ return isWidthUndefined;
+ }
+
+ private void setWidthUndefined(boolean isWidthUndefined) {
+ this.isWidthUndefined = isWidthUndefined;
+
+ if (isWidthUndefined) {
+ addStyleDependentName("Hsized");
+
+ } else {
+ removeStyleDependentName("Hsized");
+ }
+ }
+
+ private void updateCellWidth() {
+ int cellw = -1;
+ int widgetCount = getWidgetCount();
+ if (widgetCount <= 0) {
+ return;
+ }
+ if (isWidthUndefined()) {
+ Widget widget = getWidget(0);
+ String w = widget.getElement().getStyle().getWidth();
+ if (w.length() > 2) {
+ cellw = Integer.parseInt(w.substring(0, w.length() - 2));
+ }
+ } else {
+ cellw = width / getWidgetCount();
+ }
+ if (cellw > 0) {
+ for (int i = 0; i < getWidgetCount(); i++) {
+ Widget widget = getWidget(i);
+ setCellWidth(widget, cellw + "px");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java
new file mode 100644
index 0000000000..f86ba03053
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class SimpleWeekToolbar extends FlexTable implements ClickHandler {
+ private int height;
+ private VCalendar calendar;
+ private boolean isHeightUndefined;
+
+ public SimpleWeekToolbar(VCalendar parent) {
+ calendar = parent;
+ setCellSpacing(0);
+ setCellPadding(0);
+ setStyleName("v-calendar-week-numbers");
+ }
+
+ public void addWeek(int week, int year) {
+ WeekLabel l = new WeekLabel(week + "", week, year);
+ l.addClickHandler(this);
+ int rowCount = getRowCount();
+ insertRow(rowCount);
+ setWidget(rowCount, 0, l);
+ updateCellHeights();
+ }
+
+ public void updateCellHeights() {
+ if (!isHeightUndefined()) {
+ int rowCount = getRowCount();
+ if (rowCount == 0) {
+ return;
+ }
+ int cellheight = (height / rowCount) - 1;
+ int remainder = height % rowCount;
+ if (cellheight < 0) {
+ cellheight = 0;
+ }
+ for (int i = 0; i < rowCount; i++) {
+ if (remainder > 0) {
+ getWidget(i, 0).setHeight(cellheight + 1 + "px");
+ } else {
+ getWidget(i, 0).setHeight(cellheight + "px");
+ }
+ getWidget(i, 0).getElement().getStyle()
+ .setProperty("lineHeight", cellheight + "px");
+ remainder--;
+ }
+ } else {
+ for (int i = 0; i < getRowCount(); i++) {
+ getWidget(i, 0).setHeight("");
+ getWidget(i, 0).getElement().getStyle()
+ .setProperty("lineHeight", "");
+ }
+ }
+ }
+
+ public void setHeightPX(int intHeight) {
+ setHeightUndefined(intHeight == -1);
+ height = intHeight;
+ updateCellHeights();
+ }
+
+ public boolean isHeightUndefined() {
+ return isHeightUndefined;
+ }
+
+ public void setHeightUndefined(boolean isHeightUndefined) {
+ this.isHeightUndefined = isHeightUndefined;
+
+ if (isHeightUndefined) {
+ addStyleDependentName("Vsized");
+
+ } else {
+ removeStyleDependentName("Vsized");
+ }
+ }
+
+ public void onClick(ClickEvent event) {
+ WeekLabel wl = (WeekLabel) event.getSource();
+ if (calendar.getWeekClickListener() != null) {
+ calendar.getWeekClickListener().weekClick(
+ wl.getYear() + "w" + wl.getWeek());
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java
new file mode 100644
index 0000000000..c5646f97ae
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class WeekGrid extends SimplePanel {
+
+ int width = 0;
+ private int height = 0;
+ final HorizontalPanel content;
+ private VCalendar calendar;
+ private boolean disabled;
+ final Timebar timebar;
+ private Panel wrapper;
+ private boolean verticalScrollEnabled;
+ private boolean horizontalScrollEnabled;
+ private int[] cellHeights;
+ private final int slotInMinutes = 30;
+ private int dateCellBorder;
+ private DateCell dateCellOfToday;
+ private int[] cellWidths;
+ private int firstHour;
+ private int lastHour;
+
+ public WeekGrid(VCalendar parent, boolean format24h) {
+ setCalendar(parent);
+ content = new HorizontalPanel();
+ timebar = new Timebar(format24h);
+ content.add(timebar);
+
+ wrapper = new SimplePanel();
+ wrapper.setStylePrimaryName("v-calendar-week-wrapper");
+ wrapper.add(content);
+
+ setWidget(wrapper);
+ }
+
+ private void setVerticalScroll(boolean isVerticalScrollEnabled) {
+ if (isVerticalScrollEnabled && !(isVerticalScrollable())) {
+ verticalScrollEnabled = true;
+ horizontalScrollEnabled = false;
+ wrapper.remove(content);
+
+ final ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setStylePrimaryName("v-calendar-week-wrapper");
+ scrollPanel.setWidget(content);
+
+ scrollPanel.addScrollHandler(new ScrollHandler() {
+ public void onScroll(ScrollEvent event) {
+ if (calendar.getScrollListener() != null) {
+ calendar.getScrollListener().scroll(
+ scrollPanel.getVerticalScrollPosition());
+ }
+ }
+ });
+
+ setWidget(scrollPanel);
+ wrapper = scrollPanel;
+
+ } else if (!isVerticalScrollEnabled && (isVerticalScrollable())) {
+ verticalScrollEnabled = false;
+ horizontalScrollEnabled = false;
+ wrapper.remove(content);
+
+ SimplePanel simplePanel = new SimplePanel();
+ simplePanel.setStylePrimaryName("v-calendar-week-wrapper");
+ simplePanel.setWidget(content);
+
+ setWidget(simplePanel);
+ wrapper = simplePanel;
+ }
+ }
+
+ public void setVerticalScrollPosition(int verticalScrollPosition) {
+ if (isVerticalScrollable()) {
+ ((ScrollPanel) wrapper)
+ .setVerticalScrollPosition(verticalScrollPosition);
+ }
+ }
+
+ public int getInternalWidth() {
+ return width;
+ }
+
+ public void addDate(Date d) {
+ final DateCell dc = new DateCell(this, d);
+ dc.setDisabled(isDisabled());
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ dc.setVerticalSized(isVerticalScrollable());
+ content.add(dc);
+ }
+
+ /**
+ * @param dateCell
+ * @return get the index of the given date cell in this week, starting from
+ * 0
+ */
+ public int getDateCellIndex(DateCell dateCell) {
+ return content.getWidgetIndex(dateCell) - 1;
+ }
+
+ /**
+ * @return get the slot border in pixels
+ */
+ public int getDateSlotBorder() {
+ return ((DateCell) content.getWidget(1)).getSlotBorder();
+ }
+
+ private boolean isVerticalScrollable() {
+ return verticalScrollEnabled;
+ }
+
+ private boolean isHorizontalScrollable() {
+ return horizontalScrollEnabled;
+ }
+
+ public void setWidthPX(int width) {
+ if (isHorizontalScrollable()) {
+ updateCellWidths();
+
+ // Otherwise the scroll wrapper is somehow too narrow = horizontal
+ // scroll
+ wrapper.setWidth(content.getOffsetWidth()
+ + Util.getNativeScrollbarSize() + "px");
+
+ this.width = content.getOffsetWidth() - timebar.getOffsetWidth();
+
+ } else {
+ this.width = (width == -1) ? width : width
+ - timebar.getOffsetWidth();
+
+ if (isVerticalScrollable() && width != -1) {
+ this.width = this.width - Util.getNativeScrollbarSize();
+ }
+ updateCellWidths();
+ }
+ }
+
+ public void setHeightPX(int intHeight) {
+ height = intHeight;
+
+ setVerticalScroll(height <= -1);
+
+ // if not scrollable, use any height given
+ if (!isVerticalScrollable() && height > 0) {
+
+ content.setHeight(height + "px");
+ setHeight(height + "px");
+ wrapper.setHeight(height + "px");
+ wrapper.removeStyleDependentName("Vsized");
+ updateCellHeights();
+ timebar.setCellHeights(cellHeights);
+ timebar.setHeightPX(height);
+
+ } else if (isVerticalScrollable()) {
+ updateCellHeights();
+ wrapper.addStyleDependentName("Vsized");
+ timebar.setCellHeights(cellHeights);
+ timebar.setHeightPX(height);
+ }
+ }
+
+ public void clearDates() {
+ while (content.getWidgetCount() > 1) {
+ content.remove(1);
+ }
+
+ dateCellOfToday = null;
+ }
+
+ /**
+ * @return true if this weekgrid contains a date that is today
+ */
+ public boolean hasToday() {
+ return dateCellOfToday != null;
+ }
+
+ public void updateCellWidths() {
+ if (!isHorizontalScrollable() && width != -1) {
+ int count = content.getWidgetCount();
+ int datesWidth = width;
+ if (datesWidth > 0 && count > 1) {
+ cellWidths = VCalendar
+ .distributeSize(datesWidth, count - 1, -1);
+
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ dc.setWidthPX(cellWidths[i - 1]);
+ if (dc.isToday()) {
+ dc.setTimeBarWidth(getOffsetWidth());
+ }
+ }
+ }
+
+ } else {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return an int-array containing the widths of the cells (days)
+ */
+ public int[] getDateCellWidths() {
+ return cellWidths;
+ }
+
+ public void updateCellHeights() {
+ if (!isVerticalScrollable()) {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ DateCell first = (DateCell) content.getWidget(1);
+ dateCellBorder = first.getSlotBorder();
+ cellHeights = VCalendar.distributeSize(height,
+ first.getNumberOfSlots(), -dateCellBorder);
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHeightPX(height, cellHeights);
+ }
+ }
+
+ } else {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ DateCell first = (DateCell) content.getWidget(1);
+ dateCellBorder = first.getSlotBorder();
+ int dateHeight = (first.getOffsetHeight() / first
+ .getNumberOfSlots()) - dateCellBorder;
+ cellHeights = new int[48];
+ Arrays.fill(cellHeights, dateHeight);
+
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setVerticalSized(isVerticalScrollable());
+ }
+ }
+ }
+ }
+
+ public void addEvent(CalendarEvent e) {
+ int dateCount = content.getWidgetCount();
+ Date from = e.getStart();
+ Date toTime = e.getEndTime();
+ for (int i = 1; i < dateCount; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(from);
+ int comp2 = dcDate.compareTo(toTime);
+ if (comp >= 0
+ && comp2 < 0
+ || (comp == 0 && comp2 == 0 && VCalendar
+ .isZeroLengthMidnightEvent(e))) {
+ // Same event may be over two DateCells if event's date
+ // range floats over one day. It can't float over two days,
+ // because event which range is over 24 hours, will be handled
+ // as a "fullDay" event.
+ dc.addEvent(dcDate, e);
+ }
+ }
+ }
+
+ public int getPixelLengthFor(int startFromMinutes, int durationInMinutes) {
+ int pixelLength = 0;
+ int currentSlot = 0;
+
+ int firstHourInMinutes = firstHour * 60;
+
+ if (firstHourInMinutes > startFromMinutes) {
+ startFromMinutes = 0;
+ } else {
+ startFromMinutes -= firstHourInMinutes;
+ }
+
+ // calculate full slots to event
+ int slotsTillEvent = startFromMinutes / slotInMinutes;
+ int startOverFlowTime = slotInMinutes
+ - (startFromMinutes % slotInMinutes);
+ if (startOverFlowTime == slotInMinutes) {
+ startOverFlowTime = 0;
+ currentSlot = slotsTillEvent;
+ } else {
+ currentSlot = slotsTillEvent + 1;
+ }
+
+ int durationInSlots = 0;
+ int endOverFlowTime = 0;
+
+ if (startOverFlowTime > 0) {
+ durationInSlots = (durationInMinutes - startOverFlowTime)
+ / slotInMinutes;
+ endOverFlowTime = (durationInMinutes - startOverFlowTime)
+ % slotInMinutes;
+
+ } else {
+ durationInSlots = durationInMinutes / slotInMinutes;
+ endOverFlowTime = durationInMinutes % slotInMinutes;
+ }
+
+ // calculate slot overflow at start
+ if (startOverFlowTime > 0 && currentSlot < cellHeights.length) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * startOverFlowTime);
+ }
+
+ // calculate length in full slots
+ int lastFullSlot = currentSlot + durationInSlots;
+ for (; currentSlot < lastFullSlot && currentSlot < cellHeights.length; currentSlot++) {
+ pixelLength += cellHeights[currentSlot] + dateCellBorder;
+ }
+
+ // calculate overflow at end
+ if (endOverFlowTime > 0 && currentSlot < cellHeights.length) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
+ }
+
+ // reduce possible underflow at end
+ if (endOverFlowTime < 0) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
+ }
+
+ return pixelLength;
+ }
+
+ public int getPixelTopFor(int startFromMinutes) {
+ int pixelsToTop = 0;
+ int slotIndex = 0;
+
+ int firstHourInMinutes = firstHour * 60;
+
+ if (firstHourInMinutes > startFromMinutes) {
+ startFromMinutes = 0;
+ } else {
+ startFromMinutes -= firstHourInMinutes;
+ }
+
+ // calculate full slots to event
+ int slotsTillEvent = startFromMinutes / slotInMinutes;
+ int overFlowTime = startFromMinutes % slotInMinutes;
+ if (slotsTillEvent > 0) {
+ for (slotIndex = 0; slotIndex < slotsTillEvent; slotIndex++) {
+ pixelsToTop += cellHeights[slotIndex] + dateCellBorder;
+ }
+ }
+
+ // calculate lengths less than one slot
+ if (overFlowTime > 0) {
+ int lastSlotHeight = cellHeights[slotIndex] + dateCellBorder;
+ pixelsToTop += ((double) lastSlotHeight / (double) slotInMinutes)
+ * overFlowTime;
+ }
+
+ return pixelsToTop;
+ }
+
+ public void eventMoved(DateCellDayEvent dayEvent) {
+ Style s = dayEvent.getElement().getStyle();
+ int left = Integer.parseInt(s.getLeft().substring(0,
+ s.getLeft().length() - 2));
+ DateCell previousParent = (DateCell) dayEvent.getParent();
+ DateCell newParent = (DateCell) content
+ .getWidget((left / getDateCellWidth()) + 1);
+ CalendarEvent se = dayEvent.getCalendarEvent();
+ previousParent.removeEvent(dayEvent);
+ newParent.addEvent(dayEvent);
+ if (!previousParent.equals(newParent)) {
+ previousParent.recalculateEventWidths();
+ }
+ newParent.recalculateEventWidths();
+ if (calendar.getEventMovedListener() != null) {
+ calendar.getEventMovedListener().eventMoved(se);
+ }
+ }
+
+ public void setToday(Date todayDate, Date todayTimestamp) {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ if (dc.getDate().getTime() == todayDate.getTime()) {
+ if (isVerticalScrollable()) {
+ dc.setToday(todayTimestamp, -1);
+ } else {
+ dc.setToday(todayTimestamp, getOffsetWidth());
+ }
+ }
+ dateCellOfToday = dc;
+ }
+ }
+ }
+
+ public DateCell getDateCellOfToday() {
+ return dateCellOfToday;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public Timebar getTimeBar() {
+ return timebar;
+ }
+
+ public void setDateColor(Date when, Date to, String styleName) {
+ int dateCount = content.getWidgetCount();
+ for (int i = 1; i < dateCount; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(when);
+ int comp2 = dcDate.compareTo(to);
+ if (comp >= 0 && comp2 <= 0) {
+ dc.setDateColor(styleName);
+ }
+ }
+ }
+
+ /**
+ * @param calendar
+ * the calendar to set
+ */
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ /**
+ * @return the calendar
+ */
+ public VCalendar getCalendar() {
+ return calendar;
+ }
+
+ /**
+ * Get width of the single date cell
+ *
+ * @return Date cell width
+ */
+ public int getDateCellWidth() {
+ int count = content.getWidgetCount() - 1;
+ int cellWidth = -1;
+ if (count <= 0) {
+ return cellWidth;
+ }
+
+ if (width == -1) {
+ Widget firstWidget = content.getWidget(1);
+ cellWidth = firstWidget.getElement().getOffsetWidth();
+ } else {
+ cellWidth = getInternalWidth() / count;
+ }
+ return cellWidth;
+ }
+
+ /**
+ * @return the number of day cells in this week
+ */
+ public int getDateCellCount() {
+ return content.getWidgetCount() - 1;
+ }
+
+ public void setFirstHour(int firstHour) {
+ this.firstHour = firstHour;
+ timebar.setFirstHour(firstHour);
+ }
+
+ public void setLastHour(int lastHour) {
+ this.lastHour = lastHour;
+ timebar.setLastHour(lastHour);
+ }
+
+ public int getFirstHour() {
+ return firstHour;
+ }
+
+ public int getLastHour() {
+ return lastHour;
+ }
+
+ public static class Timebar extends HTML {
+
+ private static final int[] timesFor12h = { 12, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11 };
+
+ private int height;
+
+ private final int verticalPadding = 7; // FIXME measure this from DOM
+
+ private int[] slotCellHeights;
+
+ private int firstHour;
+
+ private int lastHour;
+
+ public Timebar(boolean format24h) {
+ createTimeBar(format24h);
+ }
+
+ public void setLastHour(int lastHour) {
+ this.lastHour = lastHour;
+ }
+
+ public void setFirstHour(int firstHour) {
+ this.firstHour = firstHour;
+
+ }
+
+ public void setCellHeights(int[] cellHeights) {
+ slotCellHeights = cellHeights;
+ }
+
+ private void createTimeBar(boolean format24h) {
+ setStylePrimaryName("v-calendar-times");
+
+ // Fist "time" is empty
+ Element e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerText("");
+ getElement().appendChild(e);
+
+ DateTimeService dts = new DateTimeService();
+
+ if (format24h) {
+ for (int i = firstHour + 1; i <= lastHour; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ String delimiter = dts.getClockDelimeter();
+ e.setInnerHTML("<span>" + i + "</span>" + delimiter + "00");
+ getElement().appendChild(e);
+ }
+ } else {
+ // FIXME Use dts.getAmPmStrings(); and make sure that
+ // DateTimeService has a some Locale set.
+ String[] ampm = new String[] { "AM", "PM" };
+
+ int amStop = (lastHour < 11) ? lastHour : 11;
+ int pmStart = (firstHour > 11) ? firstHour % 11 : 0;
+
+ if (firstHour < 12) {
+ for (int i = firstHour + 1; i <= amStop; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerHTML("<span>" + timesFor12h[i] + "</span>"
+ + " " + ampm[0]);
+ getElement().appendChild(e);
+ }
+ }
+
+ if (lastHour > 11) {
+ for (int i = pmStart; i < lastHour - 11; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerHTML("<span>" + timesFor12h[i] + "</span>"
+ + " " + ampm[1]);
+ getElement().appendChild(e);
+ }
+ }
+ }
+ }
+
+ public void updateTimeBar(boolean format24h) {
+ clear();
+ createTimeBar(format24h);
+ }
+
+ private void clear() {
+ while (getElement().getChildCount() > 0) {
+ getElement().removeChild(getElement().getChild(0));
+ }
+ }
+
+ public void setHeightPX(int pixelHeight) {
+ height = pixelHeight;
+
+ if (pixelHeight > -1) {
+ // as the negative margins on children pulls the whole element
+ // upwards, we must compensate. otherwise the element would be
+ // too short
+ super.setHeight((height + verticalPadding) + "px");
+ removeStyleDependentName("Vsized");
+ updateChildHeights();
+
+ } else {
+ addStyleDependentName("Vsized");
+ updateChildHeights();
+ }
+ }
+
+ private void updateChildHeights() {
+ int childCount = getElement().getChildCount();
+
+ if (height != -1) {
+
+ // 23 hours + first is empty
+ // we try to adjust the height of time labels to the distributed
+ // heights of the time slots
+ int hoursPerDay = lastHour - firstHour + 1;
+
+ int slotsPerHour = slotCellHeights.length / hoursPerDay;
+ int[] cellHeights = new int[slotCellHeights.length
+ / slotsPerHour];
+
+ int slotHeightPosition = 0;
+ for (int i = 0; i < cellHeights.length; i++) {
+ for (int j = slotHeightPosition; j < slotHeightPosition
+ + slotsPerHour; j++) {
+ cellHeights[i] += slotCellHeights[j] + 1;
+ // 1px more for borders
+ // FIXME measure from DOM
+ }
+ slotHeightPosition += slotsPerHour;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ Element e = (Element) getElement().getChild(i);
+ e.getStyle().setHeight(cellHeights[i], Unit.PX);
+ }
+
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ Element e = (Element) getElement().getChild(i);
+ e.getStyle().setProperty("height", "");
+ }
+ }
+ }
+ }
+
+ public VCalendar getParentCalendar() {
+ return calendar;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java
new file mode 100644
index 0000000000..27ace91c4e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class WeekGridMinuteTimeRange {
+ private final Date start;
+ private final Date end;
+
+ /**
+ * Creates a Date time range between start and end date. Drops seconds
+ * from the range.
+ *
+ * @param start
+ * Start time of the range
+ * @param end
+ * End time of the range
+ * @param clearSeconds
+ * Boolean Indicates, if seconds should be dropped from the
+ * range start and end
+ */
+ public WeekGridMinuteTimeRange(Date start, Date end) {
+ this.start = new Date(start.getTime());
+ this.end = new Date(end.getTime());
+ this.start.setSeconds(0);
+ this.end.setSeconds(0);
+ }
+
+ public Date getStart() {
+ return start;
+ }
+
+ public Date getEnd() {
+ return end;
+ }
+
+ public static boolean doesOverlap(WeekGridMinuteTimeRange a, WeekGridMinuteTimeRange b) {
+ boolean overlaps = a.getStart().compareTo(b.getEnd()) < 0
+ && a.getEnd().compareTo(b.getStart()) > 0;
+ return overlaps;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java
new file mode 100644
index 0000000000..bde8675435
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A label in the {@link SimpleWeekToolbar}
+ *
+ * @since 7.1
+ */
+public class WeekLabel extends Label {
+ private int week;
+ private int year;
+
+ public WeekLabel(String string, int week2, int year2) {
+ super(string);
+ setStylePrimaryName("v-calendar-week-number");
+ week = week2;
+ year = year2;
+ }
+
+ public int getWeek() {
+ return week;
+ }
+
+ public void setWeek(int week) {
+ this.week = week;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java
new file mode 100644
index 0000000000..e3b7d5d7fe
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class WeeklyLongEvents extends HorizontalPanel implements HasTooltipKey {
+
+ public static final int EVENT_HEIGTH = 15;
+
+ public static final int EVENT_MARGIN = 1;
+
+ private int rowCount = 0;
+
+ private VCalendar calendar;
+
+ private boolean undefinedWidth;
+
+ public WeeklyLongEvents(VCalendar calendar) {
+ setStylePrimaryName("v-calendar-weekly-longevents");
+ this.calendar = calendar;
+ }
+
+ public void addDate(Date d) {
+ DateCellContainer dcc = new DateCellContainer();
+ dcc.setDate(d);
+ dcc.setCalendar(calendar);
+ add(dcc);
+ }
+
+ public void setWidthPX(int width) {
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ undefinedWidth = (width < 0);
+
+ updateCellWidths();
+ }
+
+ public void addEvents(List<CalendarEvent> events) {
+ for (CalendarEvent e : events) {
+ addEvent(e);
+ }
+ }
+
+ public void addEvent(CalendarEvent calendarEvent) {
+ updateEventSlot(calendarEvent);
+
+ int dateCount = getWidgetCount();
+ Date from = calendarEvent.getStart();
+ Date to = calendarEvent.getEnd();
+ boolean started = false;
+ for (int i = 0; i < dateCount; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(from);
+ int comp2 = dcDate.compareTo(to);
+ WeeklyLongEventsDateCell eventLabel = dc.getDateCell(calendarEvent.getSlotIndex());
+ eventLabel.setStylePrimaryName("v-calendar-event");
+ if (comp >= 0 && comp2 <= 0) {
+ eventLabel.setEvent(calendarEvent);
+ eventLabel.setCalendar(calendar);
+
+ eventLabel.addStyleDependentName("all-day");
+ if (comp == 0) {
+ eventLabel.addStyleDependentName("start");
+ }
+ if (comp2 == 0) {
+ eventLabel.addStyleDependentName("end");
+ }
+ if (!started && comp > 0 && comp2 <= 0) {
+ eventLabel.addStyleDependentName("continued-from");
+ } else if (i == (dateCount - 1)) {
+ eventLabel.addStyleDependentName("continued-to");
+ }
+ final String extraStyle = calendarEvent.getStyleName();
+ if (extraStyle != null && extraStyle.length() > 0) {
+ eventLabel.addStyleDependentName(extraStyle + "-all-day");
+ }
+ if (!started) {
+ eventLabel.setText(calendarEvent.getCaption());
+ started = true;
+ }
+ }
+ }
+ }
+
+ private void updateEventSlot(CalendarEvent e) {
+ boolean foundFreeSlot = false;
+ int slot = 0;
+ while (!foundFreeSlot) {
+ if (isSlotFree(slot, e.getStart(), e.getEnd())) {
+ e.setSlotIndex(slot);
+ foundFreeSlot = true;
+
+ } else {
+ slot++;
+ }
+ }
+ }
+
+ private boolean isSlotFree(int slot, Date start, Date end) {
+ int dateCount = getWidgetCount();
+
+ // Go over all dates this week
+ for (int i = 0; i < dateCount; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(start);
+ int comp2 = dcDate.compareTo(end);
+
+ // check if the date is in the range we need
+ if (comp >= 0 && comp2 <= 0) {
+
+ // check if the slot is taken
+ if (dc.hasEvent(slot)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ public void updateCellWidths() {
+ int cells = getWidgetCount();
+ if (cells <= 0) {
+ return;
+ }
+
+ int cellWidth = -1;
+
+ // if width is undefined, use the width of the first cell
+ // otherwise use distributed sizes
+ if (undefinedWidth) {
+ cellWidth = calendar.getWeekGrid().getDateCellWidth()
+ - calendar.getWeekGrid().getDateSlotBorder();
+ }
+
+ for (int i = 0; i < cells; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+
+ if (undefinedWidth) {
+ dc.setWidth(cellWidth + "px");
+
+ } else {
+ dc.setWidth(calendar.getWeekGrid().getDateCellWidths()[i]
+ + "px");
+ }
+ }
+ }
+
+ @Override
+ public String getTooltipKey() {
+ return null;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java
new file mode 100644
index 0000000000..a97d352e81
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * Represents a cell used in {@link WeeklyLongEvents}
+ *
+ * @since 7.1
+ */
+public class WeeklyLongEventsDateCell extends HTML implements HasTooltipKey {
+ private Date date;
+ private CalendarEvent calendarEvent;
+ private VCalendar calendar;
+
+ public WeeklyLongEventsDateCell() {
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setEvent(CalendarEvent event) {
+ calendarEvent = event;
+ }
+
+ public CalendarEvent getEvent() {
+ return calendarEvent;
+ }
+
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public VCalendar getCalendar() {
+ return calendar;
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ if (calendarEvent != null) {
+ return calendarEvent.getIndex();
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java
new file mode 100644
index 0000000000..03db4d091e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ui.calendar.CalendarConnector;
+import com.vaadin.client.ui.dd.VAbstractDropHandler;
+
+/**
+ * Abstract base class for calendar drop handlers.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public abstract class CalendarDropHandler extends VAbstractDropHandler {
+
+ protected CalendarConnector calendarConnector;
+
+ /**
+ * Set the calendar instance
+ *
+ * @param calendarPaintable
+ */
+ public void setConnector(CalendarConnector calendarConnector) {
+ this.calendarConnector = calendarConnector;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#getConnector()
+ */
+ @Override
+ public CalendarConnector getConnector() {
+ return calendarConnector;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VDropHandler#getApplicationConnection
+ * ()
+ */
+ public ApplicationConnection getApplicationConnection() {
+ return calendarConnector.getClient();
+ }
+
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java
new file mode 100644
index 0000000000..6e57fb6fef
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragEvent;
+
+/**
+ * Handles DD when the monthly view is showing in the Calendar. In the monthly
+ * view, drops are only allowed in the the day cells. Only the day index is
+ * included in the drop details sent to the server.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarMonthDropHandler extends CalendarDropHandler {
+
+ private Element currentTargetElement;
+ private SimpleDayCell currentTargetDay;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragAccepted
+ * (com.vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ deEmphasis();
+ currentTargetElement = drag.getElementOver();
+ currentTargetDay = Util.findWidget(currentTargetElement,
+ SimpleDayCell.class);
+ emphasis();
+ }
+
+ /**
+ * Removed the emphasis CSS style name from the currently emphasized day
+ */
+ private void deEmphasis() {
+ if (currentTargetElement != null && currentTargetDay != null) {
+ currentTargetDay.removeEmphasisStyle();
+ currentTargetElement = null;
+ }
+ }
+
+ /**
+ * Add CSS style name for the currently emphasized day
+ */
+ private void emphasis() {
+ if (currentTargetElement != null && currentTargetDay != null) {
+ currentTargetDay.addEmphasisStyle();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragOver(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragOver(final VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ validate(new VAcceptCallback() {
+ public void accepted(VDragEvent event) {
+ dragAccepted(drag);
+ }
+ }, drag);
+ }
+ }
+
+ /**
+ * Checks if the one can perform a drop in a element
+ *
+ * @param elementOver
+ * The element to check
+ * @return
+ */
+ private boolean isLocationValid(
+ com.google.gwt.user.client.Element elementOver) {
+ com.google.gwt.user.client.Element monthGridElement = calendarConnector
+ .getWidget().getMonthGrid().getElement();
+
+ // drops are not allowed in:
+ // - weekday header
+ // - week number bart
+ return DOM.isOrHasChild(monthGridElement, elementOver);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragEnter(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ // NOOP, we determine drag acceptance in dragOver
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#drop(com.vaadin
+ * .terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public boolean drop(VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ updateDropDetails(drag);
+ deEmphasis();
+ return super.drop(drag);
+
+ } else {
+ deEmphasis();
+ return false;
+ }
+ }
+
+ /**
+ * Updates the drop details sent to the server
+ *
+ * @param drag
+ * The drag event
+ */
+ private void updateDropDetails(VDragEvent drag) {
+ int dayIndex = calendarConnector.getWidget().getMonthGrid()
+ .getDayCellIndex(currentTargetDay);
+
+ drag.getDropDetails().put("dropDayIndex", dayIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragLeave(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis();
+ super.dragLeave(drag);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java
new file mode 100644
index 0000000000..fa7aaa428b
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.calendar.schedule.DateCell;
+import com.vaadin.client.ui.calendar.schedule.DateCellDayEvent;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragEvent;
+
+/**
+ * Handles DD when the weekly view is showing in the Calendar. In the weekly
+ * view, drops are only allowed in the the time slots for each day. The slot
+ * index and the day index are included in the drop details sent to the server.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarWeekDropHandler extends CalendarDropHandler {
+
+ private com.google.gwt.user.client.Element currentTargetElement;
+ private DateCell currentTargetDay;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragAccepted
+ * (com.vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ deEmphasis();
+ currentTargetElement = drag.getElementOver();
+ currentTargetDay = Util
+ .findWidget(currentTargetElement, DateCell.class);
+ emphasis();
+ }
+
+ /**
+ * Removes the CSS style name from the emphasized element
+ */
+ private void deEmphasis() {
+ if (currentTargetElement != null) {
+ currentTargetDay.removeEmphasisStyle(currentTargetElement);
+ currentTargetElement = null;
+ }
+ }
+
+ /**
+ * Add a CSS stylen name to current target element
+ */
+ private void emphasis() {
+ currentTargetDay.addEmphasisStyle(currentTargetElement);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragOver(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragOver(final VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ validate(new VAcceptCallback() {
+ public void accepted(VDragEvent event) {
+ dragAccepted(drag);
+ }
+ }, drag);
+ }
+ }
+
+ /**
+ * Checks if the location is a valid drop location
+ *
+ * @param elementOver
+ * The element to check
+ * @return
+ */
+ private boolean isLocationValid(
+ com.google.gwt.user.client.Element elementOver) {
+ com.google.gwt.user.client.Element weekGridElement = calendarConnector
+ .getWidget().getWeekGrid().getElement();
+ com.google.gwt.user.client.Element timeBarElement = calendarConnector
+ .getWidget().getWeekGrid().getTimeBar().getElement();
+
+ com.google.gwt.user.client.Element todayBarElement = null;
+ if (calendarConnector.getWidget().getWeekGrid().hasToday()) {
+ todayBarElement = (Element) calendarConnector.getWidget()
+ .getWeekGrid().getDateCellOfToday().getTodaybarElement();
+ }
+
+ // drops are not allowed in:
+ // - weekday header
+ // - allday event list
+ // - todaybar
+ // - timebar
+ // - events
+ return DOM.isOrHasChild(weekGridElement, elementOver)
+ && !DOM.isOrHasChild(timeBarElement, elementOver)
+ && todayBarElement != elementOver
+ && (Util.findWidget(elementOver, DateCellDayEvent.class) == null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragEnter(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ // NOOP, we determine drag acceptance in dragOver
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#drop(com.vaadin
+ * .terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public boolean drop(VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ updateDropDetails(drag);
+ deEmphasis();
+ return super.drop(drag);
+
+ } else {
+ deEmphasis();
+ return false;
+ }
+ }
+
+ /**
+ * Update the drop details sent to the server
+ *
+ * @param drag
+ * The drag event
+ */
+ private void updateDropDetails(VDragEvent drag) {
+ int slotIndex = currentTargetDay.getSlotIndex(currentTargetElement);
+ int dayIndex = calendarConnector.getWidget().getWeekGrid()
+ .getDateCellIndex(currentTargetDay);
+
+ drag.getDropDetails().put("dropDayIndex", dayIndex);
+ drag.getDropDetails().put("dropSlotIndex", slotIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragLeave(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis();
+ super.dragLeave(drag);
+ }
+}