fi.jasoft.simplecalendar.client.SimpleCalendarWidget.java Source code

Java tutorial

Introduction

Here is the source code for fi.jasoft.simplecalendar.client.SimpleCalendarWidget.java

Source

/*
 * Copyright 2013 John Ahlroos
 *
 * 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 fi.jasoft.simplecalendar.client;

import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
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.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.DateTimeService;
import com.vaadin.client.LocaleNotLoadedException;
import com.vaadin.client.LocaleService;
import com.vaadin.client.Util;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.FocusableFlowPanel;

public class SimpleCalendarWidget extends FocusableFlowPanel
        implements ClickHandler, FocusHandler, BlurHandler, KeyDownHandler, MouseDownHandler {

    private static final int ROWS = 7;
    private static final int COLUMNS = 7;

    private static final String STYLENAME = "date-panel";

    private Button prevMonth;
    private Button prevYear;
    private Button nextMonth;
    private Button nextYear;

    private HTML monthAndYear;

    private HorizontalPanel controls;
    private Grid grid;

    private int day;
    private int month;
    private int year;

    private int displayedMonth;
    private int displayedYear;

    private Date today = new Date();

    private Date lastSelection;
    private Set<Date> selected = new HashSet<Date>();

    private boolean isMultiSelect = false;

    private Date startDate = null;
    private Date endDate = null;

    private Date[] disabledDates;
    private int[] disabledWeekdays;
    private int[] disabledMonthDates;

    private DateCell focusedCell;

    private String previousHeight;

    protected String descriptionDateFormat = "EEEE, MMMM dd, yyyy";

    private final Set<DateValueChangeListener> valueChangeListeners = new HashSet<DateValueChangeListener>();

    private final DateTimeService dts = new DateTimeService();

    /**
     * Represents a cell in the date grid
     */
    protected class DateCell extends HTML {
        private int row;
        private int column;
        private Date date;

        public DateCell(int row, int column, Date date) {
            this.row = row;
            this.column = column;
            this.date = date;
            setHTML(date.getDate() + "");
            DateTimeFormat fmt = DateTimeFormat.getFormat(descriptionDateFormat);
            setTitle(fmt.format(date));
        }

        public int getRow() {
            return row;
        }

        public int getColumn() {
            return column;
        }

        public Date getDate() {
            return date;
        }
    }

    /**
     * Default constructor
     */
    public SimpleCalendarWidget() {

        // Set default date to today
        day = today.getDate();
        month = today.getMonth();
        year = today.getYear() + 1900;

        setStyleName(STYLENAME);

        // Add keyboard handlers
        addFocusHandler(this);
        addBlurHandler(this);
        addKeyDownHandler(this);

        // Create the header
        controls = new HorizontalPanel();
        controls.setStyleName(getStyleName() + "-controls");
        controls.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
        add(controls);

        prevYear = new Button("", new ClickHandler() {
            public void onClick(ClickEvent event) {
                previousYear();
            }
        });

        prevYear.setTabIndex(-1);
        prevYear.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                // Prevent focus from moving to button
                event.preventDefault();
            }
        });

        prevYear.setStyleName(controls.getStyleName() + "-prev-year");
        controls.add(prevYear);
        controls.setCellHorizontalAlignment(prevYear, HasHorizontalAlignment.ALIGN_LEFT);
        controls.setCellWidth(prevYear, "35px");

        prevMonth = new Button("", new ClickHandler() {
            public void onClick(ClickEvent event) {
                previousMonth();
            }
        });

        prevMonth.setTabIndex(-1);
        prevMonth.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                // Prevent focus from moving to button
                event.preventDefault();
            }
        });

        prevMonth.setStyleName(controls.getStyleName() + "-prev-month");
        controls.add(prevMonth);
        controls.setCellHorizontalAlignment(prevMonth, HasHorizontalAlignment.ALIGN_LEFT);
        controls.setCellWidth(prevMonth, "35px");

        monthAndYear = new HTML("");
        monthAndYear.setStyleName(controls.getStyleName() + "-caption");
        controls.add(monthAndYear);
        controls.setCellHorizontalAlignment(monthAndYear, HasHorizontalAlignment.ALIGN_CENTER);

        nextMonth = new Button("", new ClickHandler() {
            public void onClick(ClickEvent event) {
                nextMonth();
            }
        });

        nextMonth.setTabIndex(-1);
        nextMonth.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                // Prevent focus from moving to button
                event.preventDefault();
            }
        });

        nextMonth.setStyleName(controls.getStyleName() + "-next-month");
        controls.add(nextMonth);
        controls.setCellHorizontalAlignment(nextMonth, HasHorizontalAlignment.ALIGN_RIGHT);
        controls.setCellWidth(nextMonth, "35px");

        nextYear = new Button("", new ClickHandler() {
            public void onClick(ClickEvent event) {
                nextYear();
            }
        });

        nextYear.setTabIndex(-1);
        nextYear.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                // Prevent focus from moving to button
                event.preventDefault();
            }
        });

        nextYear.setStyleName(controls.getStyleName() + "-next-year");
        controls.add(nextYear);
        controls.setCellHorizontalAlignment(nextYear, HasHorizontalAlignment.ALIGN_RIGHT);
        controls.setCellWidth(nextYear, "35px");

        // Create day grid
        grid = new Grid(ROWS, COLUMNS);
        grid.setStyleName(getStyleName() + "-grid");
        grid.setCellSpacing(0);
        grid.setCellPadding(0);
        grid.addClickHandler(this);
        grid.addDomHandler(this, MouseDownEvent.getType());
        grid.setWidth("100%");
        add(grid);

        // Format first row
        grid.getRowFormatter().setStyleName(0, grid.getStyleName() + "-days");

        // Populate grid with cells
        for (int r = 1; r < ROWS; r++) {
            for (int c = 0; c < COLUMNS; c++) {
                grid.setHTML(r, c, "");
                grid.getCellFormatter().setStyleName(r, c, grid.getStyleName() + "-day");
                grid.getCellFormatter().setHeight(r, c, "39px");
            }
            grid.getRowFormatter().setStyleName(r, grid.getStyleName() + "-week");
        }

        displayedMonth = month;
        displayedYear = year;
    }

    private void updateWeekdays() {
        for (int c = 0; c < COLUMNS; c++) {
            int index = (dts.getFirstDayOfWeek() + c) % COLUMNS;
            String dayName = dts.getShortDay(index);
            dayName = String.valueOf(Character.toUpperCase(dayName.charAt(0))) + dayName.substring(1);
            grid.setHTML(0, c, dayName);
            grid.getCellFormatter().setStyleName(0, c, grid.getStyleName() + "-day-caption");
        }
    }

    /**
     * Moves keyboard focus to the previous day, changes year and month in the
     * UI if necessary
     */
    private void previousDay() {
        if (focusedCell == null) {
            // If we have no focus the return
            return;
        }

        int date = focusedCell.getDate().getDate();

        if (date == 1) {
            /*
             * If we are at day 1 of a month and go back then we have to jump to
             * the last day of the previous month.
             */

            // First day of the month, need to change UI first
            previousMonth();

            // Select last day of month
            setKeyboardFocus(getDaysInMonth(displayedYear, displayedMonth));

        } else {
            // Focus the previous day
            setKeyboardFocus(date - 1);
        }
    }

    private void nextDay() {
        if (focusedCell == null) {
            // If we have no focus the return
            return;
        }

        int daysInMonth = getDaysInMonth(displayedYear, displayedMonth);

        int date = focusedCell.getDate().getDate();

        if (date == daysInMonth) {
            /*
             * We are at the last day of the month, so we first need to jump to
             * the next month before moving focus
             */

            // Change to next month
            nextMonth();

            // Focus the first day of the month
            setKeyboardFocus(1);

        } else {
            // Focus the next day
            setKeyboardFocus(date + 1);
        }
    }

    private void previousWeek() {
        if (focusedCell == null) {
            // If we have no focus the return
            return;
        }

        if (focusedCell.getDate().getDate() <= 7) {
            previousMonth();

            int diff = 7 - focusedCell.getDate().getDate();
            setKeyboardFocus(getDaysInMonth(displayedYear, displayedMonth) - diff);

        } else {
            // Move focus 7 days backward
            setKeyboardFocus(focusedCell.getDate().getDate() - 7);
        }
    }

    private void nextWeek() {
        if (focusedCell == null) {
            // If we have no focus the return
            return;
        }

        int daysInMonth = getDaysInMonth(displayedYear, displayedMonth);
        if (focusedCell.getDate().getDate() >= daysInMonth - 7) {

            // Calculate how many days we should jump in the next month
            int diff = daysInMonth - focusedCell.getDate().getDate();

            // Change to next month
            nextMonth();

            // Set focus
            setKeyboardFocus(7 - diff);

        } else {
            // Move focus 7 days forward
            setKeyboardFocus(focusedCell.getDate().getDate() + 7);
        }
    }

    /**
     * Changes UI to display the previous month
     */
    private void previousMonth() {
        int month = displayedMonth - 1;
        int year = displayedYear;
        if (month < 0) {
            year--;
            month = 11;
        }

        if (withinDateRange(new Date(year - 1900, month, 31))) {
            updateUI(year, month, 1);
            nextMonth.setEnabled(true);
        }
    }

    /**
     * Changes UI to display the next month
     */
    private void nextMonth() {
        int month = displayedMonth + 1;
        int year = displayedYear;
        if (month > 11) {
            year++;
            month = 0;
        }
        if (withinDateRange(new Date(year - 1900, month, 1))) {
            updateUI(year, month, 1);
            prevMonth.setEnabled(true);
        }
    }

    /**
     * Changes UI to display the previous year
     */
    private void previousYear() {
        int year = displayedYear - 1;
        if (withinDateRange(new Date(year - 1900, displayedMonth, 31))) {
            updateUI(year, displayedMonth, 1);
            nextYear.setEnabled(true);
        }
    }

    /**
     * Changes UI to display the next year
     */
    private void nextYear() {
        int year = displayedYear + 1;
        if (withinDateRange(new Date(year - 1900, displayedMonth, 1))) {
            updateUI(year, displayedMonth, 1);
            prevYear.setEnabled(true);
        }
    }

    /**
     * Creates a date cell in the grid. Override this to customize the cells
     * 
     * @param row
     *            The row of the cell
     * @param column
     *            The column of the cell
     * @param date
     *            The date the cell represents
     * @return
     */
    protected Widget createCell(int row, int column, Date date) {
        return new DateCell(row, column, date);
    }

    private void calculateRowHeights() {
        int totalHeight = Util.getRequiredHeight(this)
                - Util.measureVerticalPaddingAndBorder(grid.getElement(), 10);
        double rowHeight = totalHeight / ROWS;

        for (int r = 0; r < ROWS; r++) {
            for (int c = 0; c < COLUMNS; c++) {
                if (r > 0) {
                    grid.getCellFormatter().getElement(r, c).getStyle().setHeight(rowHeight, Unit.PX);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.gwt.user.client.ui.UIObject#setHeight(java.lang.String)
     */
    @Override
    public void setHeight(String height) {
        super.setHeight(height);
        if (height != previousHeight) {
            calculateRowHeights();
            previousHeight = height;
        }
    }

    private void setKeyboardFocus(int row, int column) {
        if (focusedCell != null) {
            grid.getCellFormatter().removeStyleName(focusedCell.getRow(), focusedCell.getColumn(), "focus");
        }

        try {
            focusedCell = (DateCell) grid.getWidget(row, column);
        } catch (IndexOutOfBoundsException ioobe) {
            Window.alert(ioobe + "");
            return;
        }

        if (focusedCell != null) {
            grid.getCellFormatter().addStyleName(focusedCell.getRow(), focusedCell.getColumn(), "focus");
        }
    }

    private void setKeyboardFocus(int date) {
        for (int row = 1; row < grid.getRowCount(); row++) {
            for (int col = 0; col < grid.getColumnCount(); col++) {
                DateCell cell = (DateCell) grid.getWidget(row, col);
                if (cell != null) {
                    Date d = cell.getDate();
                    if (d != null) {
                        int month = d.getMonth();
                        int day = d.getDate();
                        if (month == displayedMonth && day == date) {
                            setKeyboardFocus(row, col);
                            return;
                        }
                    }
                }
            }
        }
    }

    private void select(DateCell dc, boolean ctrl, boolean shift) {
        Date date = dc.getDate();

        // Abort if disabled
        if (dateIsDisabled(date)) {
            return;
        }

        // Set keyboard focus
        focusedCell = dc;

        if (isMultiSelect && ctrl) {
            if (dateIsSelected(date)) {
                unselect(date, false);
            } else {
                select(date);
            }

            fireValueChangeEvent();

        } else if (isMultiSelect && shift) {
            removeSelections();

            // Select a range
            if (lastSelection != null) {
                if (lastSelection.after(date)) {
                    selected.addAll(GwtDateUtil.getDatesBetween(date, lastSelection));
                } else {
                    selected.addAll(GwtDateUtil.getDatesBetween(lastSelection, date));
                }

                updateUI(displayedYear, displayedMonth, 1);
            }

            fireValueChangeEvent();

        } else {
            if (dateIsSelected(date)) {
                // Re-selection removes the selection
                removeSelections();

            } else {
                // remove previous selections
                removeSelections();

                // Select date
                select(date);
            }

            fireValueChangeEvent();
        }
    }

    public void onClick(ClickEvent event) {

        // Only handle left button clicks
        if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
            return;
        }

        Cell cell = grid.getCellForEvent(event);
        Widget w = grid.getWidget(cell.getRowIndex(), cell.getCellIndex());
        setKeyboardFocus(cell.getRowIndex(), cell.getCellIndex());

        if (w instanceof DateCell) {
            DateCell dc = (DateCell) w;
            select(dc, event.isControlKeyDown(), event.isShiftKeyDown());

        }
    }

    private void removeSelections() {
        for (int r = 1; r < ROWS; r++) {
            for (int c = 0; c < COLUMNS; c++) {
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-selected");
            }
        }
        selected.clear();
    }

    private static boolean withinDateRange(Date date, Date startDate, Date endDate) {
        boolean isAfterStartDate = false;
        if (startDate == null || GwtDateUtil.dateEqualResolutionDay(startDate, date) || startDate.before(date)) {
            isAfterStartDate = true;
        }

        boolean isBeforeEndDate = false;
        if (endDate == null || GwtDateUtil.dateEqualResolutionDay(endDate, date) || endDate.after(date)) {
            isBeforeEndDate = true;
        }

        return isAfterStartDate && isBeforeEndDate;
    }

    private boolean withinDateRange(Date date) {
        return withinDateRange(date, startDate, endDate);
    }

    private boolean isWeekdayDisabled(int weekday) {
        if (disabledWeekdays != null) {
            for (int i = 0; i < disabledWeekdays.length; i++) {
                if (disabledWeekdays[i] == weekday) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isMonthDateDisabled(int date) {
        if (disabledMonthDates != null) {
            for (int i = 0; i < disabledMonthDates.length; i++) {
                if (disabledMonthDates[i] == date) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isDateDisabled(Date date) {
        if (disabledDates != null) {
            for (Date d : disabledDates) {
                if (GwtDateUtil.dateEqualResolutionDay(d, date)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dateIsDisabled(Date date) {
        return isDateDisabled(date) || isMonthDateDisabled(date.getDate()) || isWeekdayDisabled(date.getDay());
    }

    private boolean dateIsSelected(Date date) {
        for (Date d : selected) {
            if (GwtDateUtil.dateEqualResolutionDay(d, date)) {
                return true;
            }
        }
        return false;
    }

    private void updateUI() {
        Scheduler.get().scheduleEntry(new Scheduler.ScheduledCommand() {

            @Override
            public void execute() {
                updateUI(displayedYear, displayedMonth, day);
            }
        });
    }

    private void updateUI(int year, int month, int day) {

        updateWeekdays();

        displayedMonth = month;
        displayedYear = year;

        // Check that the displayed date is within the range
        Date now = new Date(year - 1900, month, day);
        if (!withinDateRange(now)) {
            if (endDate != null) {
                displayedMonth = endDate.getMonth();
                displayedYear = endDate.getYear() + 1900;
            } else if (startDate != null) {
                displayedMonth = startDate.getMonth();
                displayedYear = startDate.getYear() + 1900;
            }
        }

        prevYear.setEnabled(withinDateRange(new Date(displayedYear - 1900 - 1, displayedMonth, 31)));

        prevMonth.setEnabled(withinDateRange(new Date(displayedYear - 1900, displayedMonth - 1,
                getDaysInMonth(displayedYear - 1900, displayedMonth - 1))));

        nextMonth.setEnabled(withinDateRange(new Date(displayedYear - 1900, displayedMonth + 1, 1)));

        nextYear.setEnabled(withinDateRange(new Date(displayedYear - 1900 + 1, displayedMonth, 1)));

        Date date = new Date(displayedYear - 1900, displayedMonth, day);

        // Set month and year
        String yearName = dts.formatDate(date, "MMMM yyyy");
        yearName = String.valueOf(Character.toUpperCase(yearName.charAt(0))) + yearName.substring(1);
        monthAndYear.setHTML(yearName);

        // Populate days
        int daysInMonth = getDaysInMonth(displayedYear, displayedMonth);
        int lastMonth = displayedMonth > 0 ? displayedMonth - 1 : 11;
        int lastYear = displayedMonth > 0 ? displayedYear : displayedYear - 1;
        int daysInLastMonth = getDaysInMonth(lastYear, lastMonth);
        int d = 1;
        int nextMonthDay = 1;

        int firstWeekDay = (new Date(displayedYear - 1900, displayedMonth, 1)).getDay() - dts.getFirstDayOfWeek();

        for (int r = 1; r < ROWS; r++) {
            for (int c = 0; c < COLUMNS; c++) {

                // Clear previous styles and content
                grid.setHTML(r, c, "");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-day-previous-month");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-day-selected");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-day-next-month");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-day-today");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-selected");
                grid.getCellFormatter().removeStyleName(r, c, grid.getStyleName() + "-disabled");

                // Populate cells and add style names
                if (r == 1 && c < firstWeekDay) {
                    // Previous month
                    Date x = new Date(displayedYear - 1900, displayedMonth - 1,
                            daysInLastMonth - firstWeekDay + c + 1);
                    if (withinDateRange(x)) {
                        grid.setWidget(r, c, createCell(r, c, x));
                        grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-day-previous-month");

                        // Is the date selected?
                        if (dateIsSelected(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-selected");
                        }

                        // Is the date disabled?
                        if (dateIsDisabled(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-disabled");
                        }
                    }
                } else if (d <= daysInMonth) {
                    // This month
                    Date x = new Date(displayedYear - 1900, displayedMonth, d);
                    if (withinDateRange(x)) {
                        grid.setWidget(r, c, createCell(r, c, x));

                        // Style selected date
                        if (d == day) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-day-selected");
                        }

                        // Add a stylename for todays date
                        if (displayedMonth - 1900 == today.getYear() && displayedMonth == today.getMonth()
                                && d == today.getDate()) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-day-today");
                        }

                        // Is the date selected?
                        if (dateIsSelected(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-selected");
                        }

                        // Is the date disabled?
                        if (dateIsDisabled(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-disabled");
                        }
                    }
                    d++;

                } else if (withinDateRange(new Date(displayedYear - 1900, displayedMonth, daysInMonth))) {
                    // Next month
                    Date x;

                    if (displayedMonth == 11) {
                        x = new Date(displayedYear - 1900 + 1, 0, nextMonthDay);
                    } else {
                        x = new Date(displayedYear - 1900, displayedMonth + 1, nextMonthDay);
                    }

                    if (withinDateRange(x)) {
                        grid.setWidget(r, c, createCell(r, c, x));
                        grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-day-next-month");

                        // Is the date selected?
                        if (dateIsSelected(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-selected");
                        }

                        // Is the date disabled?
                        if (dateIsDisabled(x)) {
                            grid.getCellFormatter().addStyleName(r, c, grid.getStyleName() + "-disabled");
                        }
                    }

                    nextMonthDay++;
                }
            }
        }
    }

    private static int getDaysInMonth(int year, int month) {
        return 32 - new Date(year, month, 32).getDate();
    }

    public void setMultiSelect(boolean enabled) {
        this.isMultiSelect = enabled;
    }

    public boolean isMultiSelect() {
        return this.isMultiSelect;
    }

    public void setStartDate(Date date) {
        this.startDate = date;
        updateUI();
    }

    public Date getStartDate() {
        return this.startDate;
    }

    public void setEndDate(Date date) {
        this.endDate = date;
        updateUI();
    }

    public Date getEndDate(Date date) {
        return this.endDate;
    }

    /**
     * Weekdays which should be disabled (0-6) where 0=Sunday
     * 
     * @param days
     */
    public void setDisabledWeekDays(int... days) {
        this.disabledWeekdays = days;
        updateUI();
    }

    /**
     * Dates which should be disabled each month (1-31)
     * 
     * @param dates
     */
    public void setDisabledDates(int... dates) {
        this.disabledMonthDates = dates;
        updateUI();
    }

    /**
     * Specific disabled dates
     * 
     * @param dates
     */
    public void setDisabledDates(Date... dates) {
        this.disabledDates = dates;
        updateUI();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
     * .dom.client.FocusEvent)
     */
    public void onFocus(FocusEvent event) {
        addStyleDependentName("focused");
        if (focusedCell != null) {
            setKeyboardFocus(focusedCell.getRow(), focusedCell.getColumn());
        } else {
            setKeyboardFocus(1);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
     * .dom.client.BlurEvent)
     */
    public void onBlur(BlurEvent event) {
        removeStyleDependentName("focused");
        if (focusedCell != null) {
            grid.getCellFormatter().removeStyleName(focusedCell.getRow(), focusedCell.getColumn(), "focus");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
     * .event.dom.client.KeyDownEvent)
     */
    public void onKeyDown(KeyDownEvent event) {
        switch (event.getNativeKeyCode()) {
        case KeyCodes.KEY_LEFT: {
            if (event.isControlKeyDown() && event.isShiftKeyDown()) {
                previousYear();
            } else if (event.isShiftKeyDown()) {
                previousMonth();
            } else {
                previousDay();
            }
            event.preventDefault();
        }
            break;
        case KeyCodes.KEY_RIGHT: {
            if (event.isControlKeyDown() && event.isShiftKeyDown()) {
                nextYear();
            } else if (event.isShiftKeyDown()) {
                nextMonth();
            } else {
                nextDay();
            }
            event.preventDefault();
        }
            break;
        case KeyCodes.KEY_UP: {
            if (event.isControlKeyDown() && event.isShiftKeyDown()) {
                previousYear();
            } else if (event.isShiftKeyDown()) {
                previousMonth();
            } else {
                previousWeek();
            }
            event.preventDefault();
        }
            break;
        case KeyCodes.KEY_DOWN: {
            if (event.isControlKeyDown() && event.isShiftKeyDown()) {
                nextYear();
            } else if (event.isShiftKeyDown()) {
                nextMonth();
            } else {
                nextWeek();
            }
            event.preventDefault();
        }
            break;
        case KeyCodes.KEY_ENTER:
        case 32: { // SPACE
            if (focusedCell != null) {
                select(focusedCell, event.isControlKeyDown(), event.isShiftKeyDown());
                event.preventDefault();
            }
        }
            break;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
     * .gwt.event.dom.client.MouseDownEvent)
     */
    public void onMouseDown(MouseDownEvent event) {
        // Prevent browser default behavior when clicking on the grid
        event.preventDefault();
        setFocus(true);
    }

    /**
     * Returns the selected dates, see also {@link #getSelectedDateRange()}
     * 
     * @return
     */
    public Set<Date> getSelectedDates() {
        return selected;
    }

    /**
     * Select a new date in the calendar
     * 
     * @param date
     *       The date to select
     * @param fireValueChangeEvent
     *       Should a valuechange event be fired whne the value changes
     */
    public void select(Date date, boolean fireValueChangeEvent) {
        Date d = (Date) date.clone();
        d.setHours(0);
        d.setMinutes(0);
        d.setSeconds(0);
        selected.add(d);
        lastSelection = d;
        updateUI();
        if (fireValueChangeEvent) {
            fireValueChangeEvent();
        }
    }

    /**
     * Select a date in the panel. The dates
     * 
     * @param date
     *            The date to select
     */
    public void select(Date date) {
        select(date, true);
    }

    protected void unselect(Date date, boolean fireValueChangeEvent) {
        Set<Date> removed = new HashSet<Date>();
        for (Date d : selected) {
            if (GwtDateUtil.dateEqualResolutionDay(d, date)) {
                removed.add(d);
            }
        }

        if (!removed.isEmpty()) {
            selected.removeAll(removed);
            updateUI();
            if (fireValueChangeEvent) {
                fireValueChangeEvent();
            }
        }
    }

    /**
     * Remove a selected date from the panel
     * 
     * @param date
     *            The date to remove
     */
    public void unselect(Date date) {
        unselect(date, true);
    }

    /**
     * Remove all selections
     */
    public void unselectAll() {
        selected.clear();
        updateUI();
        fireValueChangeEvent();
    }

    /**
     * Fires a value change event with the current selection
     */
    protected void fireValueChangeEvent() {
        for (DateValueChangeListener listener : valueChangeListeners) {
            listener.valueChange(this, selected);
        }
    }

    /**
     * Adds a value change listener which listens to when the date selection
     * changes
     * 
     * @param listener
     *            The listener to add
     */
    public void addListener(DateValueChangeListener listener) {
        valueChangeListeners.add(listener);
    }

    /**
     * Removes a listener
     * 
     * @param listener
     */
    public void removeListener(DateValueChangeListener listener) {
        valueChangeListeners.remove(listener);
    }

    /**
     * The format of the date in the tooltip when hoovering over a date
     * 
     * @param format
     *            A pattern used by SimpleDateFormat
     */
    public void setDescriptionDateFormat(String format) {
        descriptionDateFormat = format;
    }

    /**
     * The format of the date in the tooltip when hoovering over a date
     */
    public String getDescriptionDateFormat() {
        return descriptionDateFormat;
    }

    /**
     * Opens the specific month and year and places the keyboard focus on that
     * day
     */
    public void showDate(Date d) {
        updateUI(d.getYear() + 1900, d.getMonth(), d.getDate());
    }

    /**
     * Sets the locale of the calendar
     * 
     * @param locale
     *       The locale string
     */
    public void setLocale(String locale) {
        try {
            dts.setLocale(locale);
            updateUI();
        } catch (LocaleNotLoadedException e) {
            getLogger().log(Level.WARNING, "Could not set locale '" + locale + "'.", e);
        }
    }

    /**
     * Returns the current locale of the calendar
     */
    public String getLocale() {
        return dts.getLocale();
    }

    private static final Logger getLogger() {
        return Logger.getLogger(SimpleCalendarWidget.class.getSimpleName());
    }
}