/* * Copyright 2000-2016 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.v7.ui; import java.lang.reflect.Method; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.EventListener; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.TargetDetails; import com.vaadin.server.KeyMapper; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; import com.vaadin.ui.LegacyComponent; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.v7.data.Container; import com.vaadin.v7.data.util.BeanItemContainer; import com.vaadin.v7.shared.ui.calendar.CalendarEventId; import com.vaadin.v7.shared.ui.calendar.CalendarServerRpc; import com.vaadin.v7.shared.ui.calendar.CalendarState; import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder; import com.vaadin.v7.shared.ui.calendar.DateConstants; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.BackwardEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.BackwardHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.DateClickEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.DateClickHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventClick; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventClickHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventMoveHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventResize; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventResizeHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.ForwardEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.ForwardHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.MoveEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.RangeSelectEvent; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.RangeSelectHandler; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.WeekClick; import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.WeekClickHandler; import com.vaadin.v7.ui.components.calendar.CalendarDateRange; import com.vaadin.v7.ui.components.calendar.CalendarTargetDetails; import com.vaadin.v7.ui.components.calendar.ContainerEventProvider; import com.vaadin.v7.ui.components.calendar.event.BasicEventProvider; import com.vaadin.v7.ui.components.calendar.event.CalendarEditableEventProvider; import com.vaadin.v7.ui.components.calendar.event.CalendarEvent; import com.vaadin.v7.ui.components.calendar.event.CalendarEvent.EventChangeEvent; import com.vaadin.v7.ui.components.calendar.event.CalendarEvent.EventChangeListener; import com.vaadin.v7.ui.components.calendar.event.CalendarEventProvider; import com.vaadin.v7.ui.components.calendar.handler.BasicBackwardHandler; import com.vaadin.v7.ui.components.calendar.handler.BasicDateClickHandler; import com.vaadin.v7.ui.components.calendar.handler.BasicEventMoveHandler; import com.vaadin.v7.ui.components.calendar.handler.BasicEventResizeHandler; import com.vaadin.v7.ui.components.calendar.handler.BasicForwardHandler; import com.vaadin.v7.ui.components.calendar.handler.BasicWeekClickHandler; /** *
* Vaadin Calendar is for visualizing events in a calendar. Calendar events can * be visualized in the variable length view depending on the start and end * dates. *
* ** Construct a Vaadin Calendar with event provider. Event provider is * obligatory, because calendar component will query active events through * it. *
* ** By default, Vaadin Calendar will show dates from the start of the current * week to the end of the current week. Use {@link #setStartDate(Date)} and * {@link #setEndDate(Date)} to change this. *
* * @param eventProvider * Event provider, cannot be null. */ public Calendar(CalendarEventProvider eventProvider) { this(null, eventProvider); } /** ** Construct a Vaadin Calendar with event provider and a caption. Event * provider is obligatory, because calendar component will query active * events through it. *
* ** By default, Vaadin Calendar will show dates from the start of the current * week to the end of the current week. Use {@link #setStartDate(Date)} and * {@link #setEndDate(Date)} to change this. *
* * @param eventProvider * Event provider, cannot be null. */ // this is the constructor every other constructor calls public Calendar(String caption, CalendarEventProvider eventProvider) { registerRpc(rpc); setCaption(caption); handlers = new HashMap<>(); setDefaultHandlers(); currentCalendar.setTime(new Date()); setEventProvider(eventProvider); getState().firstDayOfWeek = firstDay; getState().lastVisibleDayOfWeek = lastDay; getState().firstHourOfDay = firstHour; getState().lastHourOfDay = lastHour; setTimeFormat(null); } @Override public CalendarState getState() { return (CalendarState) super.getState(); } @Override protected CalendarState getState(boolean markAsDirty) { return (CalendarState) super.getState(markAsDirty); } @Override public void beforeClientResponse(boolean initial) { super.beforeClientResponse(initial); initCalendarWithLocale(); getState().format24H = TimeFormat.Format24H == getTimeFormat(); setupDaysAndActions(); setupCalendarEvents(); rpc.scroll(scrollTop); } /** * Set all the wanted default handlers here. This is always called after * constructing this object. All other events have default handlers except * range and event click. */ protected void setDefaultHandlers() { setHandler(new BasicBackwardHandler()); setHandler(new BasicForwardHandler()); setHandler(new BasicWeekClickHandler()); setHandler(new BasicDateClickHandler()); setHandler(new BasicEventMoveHandler()); setHandler(new BasicEventResizeHandler()); } /** * Gets the calendar's start date. * * @return First visible date. */ public Date getStartDate() { if (startDate == null) { currentCalendar.set(java.util.Calendar.MILLISECOND, 0); currentCalendar.set(java.util.Calendar.SECOND, 0); currentCalendar.set(java.util.Calendar.MINUTE, 0); currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0); currentCalendar.set(java.util.Calendar.DAY_OF_WEEK, currentCalendar.getFirstDayOfWeek()); return currentCalendar.getTime(); } return startDate; } /** * Sets start date for the calendar. This and {@link #setEndDate(Date)} * control the range of dates visible on the component. The default range is * one week. * * @param date * First visible date to show. */ public void setStartDate(Date date) { if (!date.equals(startDate)) { startDate = date; markAsDirty(); } } /** * Gets the calendar's end date. * * @return Last visible date. */ public Date getEndDate() { if (endDate == null) { currentCalendar.set(java.util.Calendar.MILLISECOND, 0); currentCalendar.set(java.util.Calendar.SECOND, 59); currentCalendar.set(java.util.Calendar.MINUTE, 59); currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 23); currentCalendar.set(java.util.Calendar.DAY_OF_WEEK, currentCalendar.getFirstDayOfWeek() + 6); return currentCalendar.getTime(); } return endDate; } /** * Sets end date for the calendar. Starting from startDate, only six weeks * will be shown if duration to endDate is longer than six weeks. * * This and {@link #setStartDate(Date)} control the range of dates visible * on the component. The default range is one week. * * @param date * Last visible date to show. */ public void setEndDate(Date date) { if (startDate != null && startDate.after(date)) { startDate = (Date) date.clone(); markAsDirty(); } else if (!date.equals(endDate)) { endDate = date; markAsDirty(); } } /** * Sets the locale to be used in the Calendar component. * * @see com.vaadin.ui.AbstractComponent#setLocale(java.util.Locale) */ @Override public void setLocale(Locale newLocale) { super.setLocale(newLocale); initCalendarWithLocale(); } /** * Initialize the java calendar instance with the current locale and * timezone. */ private void initCalendarWithLocale() { if (timezone != null) { currentCalendar = java.util.Calendar.getInstance(timezone, getLocale()); } else { currentCalendar = java.util.Calendar.getInstance(getLocale()); } if (customFirstDayOfWeek != null) { currentCalendar.setFirstDayOfWeek(customFirstDayOfWeek); } } private void setupCalendarEvents() { int durationInDays = (int) ((endDate.getTime() - startDate.getTime()) / DateConstants.DAYINMILLIS); durationInDays++; if (durationInDays > 60) { throw new RuntimeException( "Daterange is too big (max 60) = " + durationInDays); } Date firstDateToShow = expandStartDate(startDate, durationInDays > 7); Date lastDateToShow = expandEndDate(endDate, durationInDays > 7); currentCalendar.setTime(firstDateToShow); events = getEventProvider().getEvents(firstDateToShow, lastDateToShow); cacheMinMaxTimeOfDay(events); Listnull
.
*/
private void cacheMinMaxTimeOfDay(List* If no events exist, nothing happens. *
* NOTE: triggering this method only does this once for the current
* events - events that are not in the current visible range, are
* ignored!
*
* @see #setFirstVisibleHourOfDay(int)
* @see #setLastVisibleHourOfDay(int)
*/
public void autoScaleVisibleHoursOfDay() {
if (minTimeInMinutes != null) {
setFirstVisibleHourOfDay(minTimeInMinutes / 60);
// Do not show the final hour if last minute ends on it
setLastVisibleHourOfDay((maxTimeInMinutes - 1) / 60);
}
}
/**
* Resets the {@link #setFirstVisibleHourOfDay(int)} and
* {@link #setLastVisibleHourOfDay(int)} to the default values, 0 and 23
* respectively.
*
* @see #autoScaleVisibleHoursOfDay()
* @see #setFirstVisibleHourOfDay(int)
* @see #setLastVisibleHourOfDay(int)
*/
public void resetVisibleHoursOfDay() {
setFirstVisibleHourOfDay(0);
setLastVisibleHourOfDay(23);
}
private void setupDaysAndActions() {
// Make sure we have a up-to-date locale
initCalendarWithLocale();
CalendarState state = getState();
state.firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
// If only one is null, throw exception
// If both are null, set defaults
if (startDate == null ^ endDate == null) {
String message = "Schedule cannot be painted without a proper date range.\n";
if (startDate == null) {
throw new IllegalStateException(message
+ "You must set a start date using setStartDate(Date).");
} else {
throw new IllegalStateException(message
+ "You must set an end date using setEndDate(Date).");
}
} else if (startDate == null && endDate == null) {
// set defaults
startDate = getStartDate();
endDate = getEndDate();
}
int durationInDays = (int) ((endDate.getTime() - startDate.getTime())
/ DateConstants.DAYINMILLIS);
durationInDays++;
if (durationInDays > 60) {
throw new RuntimeException(
"Daterange is too big (max 60) = " + durationInDays);
}
state.dayNames = getDayNamesShort();
state.monthNames = getMonthNamesShort();
// Use same timezone in all dates this component handles.
// Show "now"-marker in browser within given timezone.
Date now = new Date();
currentCalendar.setTime(now);
now = currentCalendar.getTime();
// Reset time zones for custom date formats
df_date.setTimeZone(currentCalendar.getTimeZone());
df_time.setTimeZone(currentCalendar.getTimeZone());
state.now = df_date.format(now) + " " + df_time.format(now);
Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);
currentCalendar.setTime(firstDateToShow);
DateFormat weeklyCaptionFormatter = getWeeklyCaptionFormatter();
weeklyCaptionFormatter.setTimeZone(currentCalendar.getTimeZone());
Map
* This method restricts the weekdays that are shown. This affects both the
* monthly and the weekly view. The general contract is that firstDay <
* lastDay.
*
* Note that this only affects the rendering process. Events are still
* requested by the dates set by {@link #setStartDate(Date)} and
* {@link #setEndDate(Date)}.
*
* This method restricts the weekdays that are shown. This affects both the
* monthly and the weekly view. The general contract is that firstDay <
* lastDay.
*
* Note that this only affects the rendering process. Events are still
* requested by the dates set by {@link #setStartDate(Date)} and
* {@link #setEndDate(Date)}.
*
* This method restricts the hours that are shown per day. This affects the
* weekly view. The general contract is that firstHour < lastHour.
*
* Note that this only affects the rendering process. Events are still
* requested by the dates set by {@link #setStartDate(Date)} and
* {@link #setEndDate(Date)}.
*
* This method restricts the hours that are shown per day. This affects the
* weekly view. The general contract is that firstHour < lastHour.
*
* Note that this only affects the rendering process. Events are still
* requested by the dates set by {@link #setStartDate(Date)} and
* {@link #setEndDate(Date)}.
*
* The {@link Handler#getActions(Object, Object)} parameters depend on what
* view the Calendar is in:
* setTimeFormat(TimeFormat.Format12H);
* Set to null, if you want the format being defined by the locale.
*
* @param format
* Set 12h or 24h format. Default is defined by the locale.
*/
public void setTimeFormat(TimeFormat format) {
currentTimeFormat = format;
markAsDirty();
}
/**
* Returns a time zone that is currently used by this component.
*
* @return Component's Time zone
*/
public TimeZone getTimeZone() {
if (timezone == null) {
return currentCalendar.getTimeZone();
}
return timezone;
}
/**
* Set time zone that this component will use. Null value sets the default
* time zone.
*
* @param zone
* Time zone to use
*/
public void setTimeZone(TimeZone zone) {
timezone = zone;
if (!currentCalendar.getTimeZone().equals(zone)) {
if (zone == null) {
zone = TimeZone.getDefault();
}
currentCalendar.setTimeZone(zone);
df_date_time.setTimeZone(zone);
markAsDirty();
}
}
/**
* Get the internally used Calendar instance. This is the currently used
* instance of {@link java.util.Calendar} but is bound to change during the
* lifetime of the component.
*
* @return the currently used java calendar
*/
public java.util.Calendar getInternalCalendar() {
return currentCalendar;
}
/**
* Calendar.setEventProvider(new ContainerEventProvider(container))
*
* Use this method if you are adding a container which uses the default
* property ids like {@link BeanItemContainer} for instance. If you are
* using custom properties instead use
* {@link Calendar#setContainerDataSource(com.vaadin.v7.data.Container.Indexed, Object, Object, Object, Object, Object)}
*
* Please note that the container must be sorted by date!
*
* @param container
* The container to use as a datasource
*/
public void setContainerDataSource(Container.Indexed container) {
ContainerEventProvider provider = new ContainerEventProvider(container);
provider.addEventSetChangeListener(
new CalendarEventProvider.EventSetChangeListener() {
@Override
public void eventSetChange(
EventSetChangeEvent changeEvent) {
// Repaint if events change
markAsDirty();
}
});
provider.addEventChangeListener(new EventChangeListener() {
@Override
public void eventChange(EventChangeEvent changeEvent) {
// Repaint if event changes
markAsDirty();
}
});
setEventProvider(provider);
}
/**
* Sets a container as a data source for the events in the calendar.
* Equivalent for doing
* Calendar.setEventProvider(new ContainerEventProvider(container))
*
* Please note that the container must be sorted by date!
*
* @param container
* The container to use as a data source
* @param captionProperty
* The property that has the caption, null if no caption property
* is present
* @param descriptionProperty
* The property that has the description, null if no description
* property is present
* @param startDateProperty
* The property that has the starting date
* @param endDateProperty
* The property that has the ending date
* @param styleNameProperty
* The property that has the stylename, null if no stylname
* property is present
*/
public void setContainerDataSource(Container.Indexed container,
Object captionProperty, Object descriptionProperty,
Object startDateProperty, Object endDateProperty,
Object styleNameProperty) {
ContainerEventProvider provider = new ContainerEventProvider(container);
provider.setCaptionProperty(captionProperty);
provider.setDescriptionProperty(descriptionProperty);
provider.setStartDateProperty(startDateProperty);
provider.setEndDateProperty(endDateProperty);
provider.setStyleNameProperty(styleNameProperty);
provider.addEventSetChangeListener(
new CalendarEventProvider.EventSetChangeListener() {
@Override
public void eventSetChange(
EventSetChangeEvent changeEvent) {
// Repaint if events change
markAsDirty();
}
});
provider.addEventChangeListener(new EventChangeListener() {
@Override
public void eventChange(EventChangeEvent changeEvent) {
// Repaint if event changes
markAsDirty();
}
});
setEventProvider(provider);
}
/*
* (non-Javadoc)
*
* @see
* com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
* util.Date, java.util.Date)
*/
@Override
public List
*
* The Dates passed into the {@link CalendarDateRange} are in the same
* timezone as the calendar is.
*
* The {@link Handler#handleAction(Action, Object, Object)} parameters * depend on what the context menu is called upon: *
* If set to true, the captions are rendered in the browser as HTML and the * developer is responsible for ensuring no harmful HTML is used. If set to * false, the caption is rendered in the browser as plain text. *
* The default is false, i.e. to render that caption as plain text. * * @param captionAsHtml * true if the captions are rendered as HTML, false if rendered * as plain text */ public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) { getState().eventCaptionAsHtml = eventCaptionAsHtml; } /** * Checks whether event captions are rendered as HTML *
* The default is false, i.e. to render that caption as plain text.
*
* @return true if the captions are rendered as HTML, false if rendered as
* plain text
*/
public boolean isEventCaptionAsHtml() {
return getState(false).eventCaptionAsHtml;
}
@Override
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
Attributes attr = design.attributes();
if (design.hasAttr("time-format")) {
setTimeFormat(TimeFormat.valueOf(
"Format" + design.attr("time-format").toUpperCase()));
}
if (design.hasAttr("start-date")) {
setStartDate(DesignAttributeHandler.readAttribute("start-date",
attr, Date.class));
}
if (design.hasAttr("end-date")) {
setEndDate(DesignAttributeHandler.readAttribute("end-date", attr,
Date.class));
}
};
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
if (currentTimeFormat != null) {
design.attr("time-format",
currentTimeFormat == TimeFormat.Format12H ? "12h" : "24h");
}
if (startDate != null) {
design.attr("start-date", df_date.format(getStartDate()));
}
if (endDate != null) {
design.attr("end-date", df_date.format(getEndDate()));
}
if (!getTimeZone().equals(TimeZone.getDefault())) {
design.attr("time-zone", getTimeZone().getID());
}
}
@Override
protected Collection