diff options
author | John Ahlroos <john@vaadin.com> | 2013-03-27 16:33:28 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-04-03 08:03:37 +0000 |
commit | 217ba18e53a8607a9e2480574ec1c3da11f4037f (patch) | |
tree | 2c38b306985b77144e0797e8bc83d386c8575aa3 /client/src | |
parent | 1d25d6d6427f94e93e3bf7417aa968aaa9673dab (diff) | |
download | vaadin-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')
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(" "); + 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); + } +} |