com.sencha.gxt.widget.core.client.DatePicker.java Source code

Java tutorial

Introduction

Here is the source code for com.sencha.gxt.widget.core.client.DatePicker.java

Source

/**
 * Sencha GXT 4.0.0 - Sencha for GWT
 * Copyright (c) 2006-2015, Sencha Inc.
 *
 * licensing@sencha.com
 * http://www.sencha.com/products/gxt/license/
 *
 * ================================================================================
 * Open Source License
 * ================================================================================
 * This version of Sencha GXT is licensed under the terms of the Open Source GPL v3
 * license. You may use this license only if you are prepared to distribute and
 * share the source code of your application under the GPL v3 license:
 * http://www.gnu.org/licenses/gpl.html
 *
 * If you are NOT prepared to distribute and share the source code of your
 * application under the GPL v3 license, other commercial and oem licenses
 * are available for an alternate download of Sencha GXT.
 *
 * Please see the Sencha GXT Licensing page at:
 * http://www.sencha.com/products/gxt/license/
 *
 * For clarification or additional options, please contact:
 * licensing@sencha.com
 * ================================================================================
 *
 *
 * ================================================================================
 * Disclaimer
 * ================================================================================
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 * ================================================================================
 */
package com.sencha.gxt.widget.core.client;

import java.util.ArrayList;
import java.util.Date;
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.NodeList;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.i18n.shared.DateTimeFormatInfo;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.datepicker.client.CalendarUtil;
import com.sencha.gxt.core.client.GXT;
import com.sencha.gxt.core.client.Style.Direction;
import com.sencha.gxt.core.client.dom.CompositeElement;
import com.sencha.gxt.core.client.dom.CompositeFunction;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.gestures.TapGestureRecognizer;
import com.sencha.gxt.core.client.gestures.TouchData;
import com.sencha.gxt.core.client.util.DateWrapper;
import com.sencha.gxt.core.client.util.KeyNav;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.core.client.util.Util;
import com.sencha.gxt.fx.client.FxElement;
import com.sencha.gxt.fx.client.animation.AfterAnimateEvent;
import com.sencha.gxt.fx.client.animation.AfterAnimateEvent.AfterAnimateHandler;
import com.sencha.gxt.fx.client.animation.Fx;
import com.sencha.gxt.messages.client.DefaultMessages;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
import com.sencha.gxt.widget.core.client.event.XEvent;

/**
 * A date picker that displays a calendar for the specified month and provides
 * the user the ability to select the month, year and day.
 */
public class DatePicker extends Component implements HasValue<Date> {

    public enum DateState {
        ACTIVE, TODAY, SELECTED, DISABLED, OVER, PREVIOUS, NEXT
    }

    /**
     * The appearance of the date picker.
     */
    public interface DatePickerAppearance {

        String dateSelector();

        /**
         * Returns the selector that identifies the element representing a day.
         *
         * @return the day selector
         */
        String daySelector();

        NodeList<Element> getDateCells(XElement parent);

        boolean isDisabled(Element cell);

        /**
         * Returns the selector that identifies the element representing the left
         * month arrow.
         *
         * @return the left month arrow selector
         */
        String leftMonthSelector();

        /**
         * Returns the selector that identifies the element representing the left
         * year arrow (in the month / year picker).
         *
         * @return the left year arrow selector
         */
        String leftYearSelector();

        /**
         * Returns the selector that identifies the element representing the month
         * button (for displaying the month / year picker).
         *
         * @return the month button selector
         */
        String monthButtonSelector();

        /**
         * Returns the selector that identifies the element representing the month
         * picker cancel button (in the month / year picker).
         *
         * @return the month pick cancel selector
         */
        String monthPickerCancelSelector();

        /**
         * Returns the selector that identifies the elements representing the months
         * (in the month / year picker).
         *
         * @return the month picker month selector
         */
        String monthPickerMonthSelector();

        /**
         * Returns the selector that identifies the element representing the OK
         * button (in the month / year picker).
         *
         * @return the month picker OK selector
         */
        String monthPickerOkSelector();

        /**
         * Returns the selector that identifies the element representing the year
         * (in the month / year picker).
         *
         * @return the month picker year year selector
         */
        String monthPickerYearSelector();

        void onMonthButtonHtmlChange(XElement parent, SafeHtml html);

        void onMonthPickerSize(XElement monthPicker, int width, int height);

        void onMonthSelected(Element cell, boolean select);

        void onHtmlChange(Element cell, SafeHtml html);

        void onUpdateDateStyle(Element cell, DateState state, boolean add);

        void onUpdateDayOfWeeks(XElement parent, List<SafeHtml> days);

        /**
         * Renders the appearance of a date picker as HTML into a
         * {@link SafeHtmlBuilder}, suitable for passing to
         * {@link Element#setInnerSafeHtml(SafeHtml)} on a container element.
         *
         * @param sb receives the rendered appearance
         */
        void render(SafeHtmlBuilder sb);

        /**
         * Renders the appearance of a month / year picker as HTML into a
         * {@link SafeHtmlBuilder}, suitable for passing to
         * {@link Element#setInnerSafeHtml(SafeHtml)} on a container element.
         *
         * @param sb         receives the rendered appearance
         * @param messages   the translatable messages (e.g. for ToolTips)
         * @param monthNames the month names
         */
        void renderMonthPicker(SafeHtmlBuilder sb, DatePickerMessages messages, String[] monthNames);

        /**
         * Returns the selector that identifies the element representing the right
         * month arrow.
         *
         * @return the right month arrow selector
         */
        String rightMonthSelector();

        /**
         * Returns the selector that identifies the element representing the right
         * year arrow (in the month / year picker).
         *
         * @return the right year arrow selector
         */
        String rightYearSelector();

        /**
         * Returns the selector that identifies the element representing the today
         * button.
         *
         * @return the today button selector
         */
        String todayButtonSelector();

    }

    /**
     * The translatable strings (e.g. button text and ToolTips) for date picker.
     */
    public static interface DatePickerMessages {

        /**
         * Returns the "Cancel" button text.
         *
         * @return the "Cancel" button text
         */
        String cancelText();

        /**
         * Returns the advisory title of elements after the maximum date.
         *
         * @return the advisory title of elements after the maximum date.
         */
        String maxText();

        /**
         * Returns the advisory title of elements before the maximum date.
         *
         * @return the advisory title of elements before the maximum date.
         */
        String minText();

        /**
         * Returns the advisory title of the month / year element.
         *
         * @return the advisory title of the month / year element
         */
        String monthYearText();

        /**
         * Returns the advisory title of the next month element.
         *
         * @return the advisory title of the next month element.
         */
        String nextText();

        /**
         * Returns the "OK" button text.
         *
         * @return the "OK" button text
         */
        String okText();

        /**
         * Returns the advisory title of the previous month element.
         *
         * @return the advisory title of the previous month element.
         */
        String prevText();

        /**
         * Returns the "Today" button text.
         *
         * @return the "Today" button text
         */
        String todayText();

        /**
         * Returns the ToolTip to use with the Today button.
         *
         * @param date formatted representation of today's date
         * @return the ToolTip for the Today button
         */
        String todayTip(String date);
    }

    /**
     * The translatable strings (e.g. button text and ToolTips) for date picker.
     */
    public class DatePickerDefaultMessages implements DatePickerMessages {

        @Override
        public String cancelText() {
            return DefaultMessages.getMessages().datePicker_cancelText();
        }

        @Override
        public String maxText() {
            return DefaultMessages.getMessages().datePicker_maxText();
        }

        @Override
        public String minText() {
            return DefaultMessages.getMessages().datePicker_minText();
        }

        @Override
        public String monthYearText() {
            return DefaultMessages.getMessages().datePicker_monthYearText();
        }

        @Override
        public String nextText() {
            return DefaultMessages.getMessages().datePicker_nextText();
        }

        @Override
        public String okText() {
            return DefaultMessages.getMessages().datePicker_okText();
        }

        @Override
        public String prevText() {
            return DefaultMessages.getMessages().datePicker_prevText();
        }

        @Override
        public String todayText() {
            return DefaultMessages.getMessages().datePicker_todayText();
        }

        @Override
        public String todayTip(String date) {
            return DefaultMessages.getMessages().datePicker_todayTip(date);
        }

    }

    /**
     * Provides the date time info constants the date picker. Default values use the GWT {@link DateTimeFormat} returned
     * by {@code LocaleInfo.getCurrentLocale().getDateTimeFormatInfo()}. Modifications to the default values will only be
     * applied when {@link #setDateTimeInfo(com.sencha.gxt.widget.core.client.DatePicker.DatePickerDateTimeFormatInfo)} is
     * called.
     */
    public static class DatePickerDateTimeFormatInfo {

        private int firstDayOfTheWeek;
        private String[] weekdaysNarrow;
        private String[] monthsShort;
        private String[] monthsFullStandalone;

        public DatePickerDateTimeFormatInfo() {
            DateTimeFormatInfo info = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo();
            firstDayOfTheWeek = info.firstDayOfTheWeek();
            weekdaysNarrow = info.weekdaysNarrow();
            monthsShort = info.monthsShort();
            monthsFullStandalone = info.monthsFullStandalone();
        }

        public int getFirstDayOfTheWeek() {
            return firstDayOfTheWeek;
        }

        public void setFirstDayOfTheWeek(int firstDayOfTheWeek) {
            this.firstDayOfTheWeek = firstDayOfTheWeek;
        }

        public String[] getWeekdaysNarrow() {
            return weekdaysNarrow;
        }

        public void setWeekdaysNarrow(String[] weekdaysNarrow) {
            this.weekdaysNarrow = weekdaysNarrow;
        }

        public String[] getMonthsShort() {
            return monthsShort;
        }

        public void setMonthsShort(String[] monthsShort) {
            this.monthsShort = monthsShort;
        }

        public String[] getMonthsFullStandalone() {
            return monthsFullStandalone;
        }

        public void setMonthsFullStandalone(String[] monthsFullStandalone) {
            this.monthsFullStandalone = monthsFullStandalone;
        }
    }

    private final DatePickerAppearance appearance;
    protected TextButton todayBtn;
    protected TextButton monthPickerOkButton;
    protected TextButton monthPickerCancelButton;
    protected DateWrapper activeDate, value;
    protected long today;
    protected Date maxDate, minDate;
    protected Element[] cells;
    protected int startDay = Integer.MIN_VALUE;
    protected XElement monthPicker;
    protected CompositeElement mpMonths, mpYears;
    protected int mpSelMonth, mpSelYear;
    protected int mpyear;
    protected DatePickerMessages messages;
    protected XElement overElement;
    protected DatePickerDateTimeFormatInfo dateTimeInfo = GWT.create(DatePickerDateTimeFormatInfo.class);

    /**
     * Creates a date picker with the default appearance.
     */
    public DatePicker() {
        this(GWT.<DatePickerAppearance>create(DatePickerAppearance.class));
    }

    /**
     * Creates a date picker with the specified appearance.
     *
     * @param appearance the appearance of the date picker
     */
    public DatePicker(DatePickerAppearance appearance) {
        this.appearance = appearance;

        SafeHtmlBuilder builder = new SafeHtmlBuilder();
        this.appearance.render(builder);

        setElement((Element) XDOM.create(builder.toSafeHtml()));

        setAllowTextSelection(false);

        setStartDay(dateTimeInfo.getFirstDayOfTheWeek());

        todayBtn = new TextButton(getMessages().todayText());
        todayBtn.addSelectHandler(new SelectHandler() {

            @Override
            public void onSelect(SelectEvent event) {
                selectToday();
            }
        });

        todayBtn.setToolTip(
                getMessages().todayTip(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT).format(new Date())));

        getElement().selectNode(appearance.todayButtonSelector()).appendChild(todayBtn.getElement());

        monthPickerOkButton = new TextButton(getMessages().okText());
        monthPickerOkButton.addSelectHandler(new SelectHandler() {
            @Override
            public void onSelect(SelectEvent event) {
                DateWrapper d = new DateWrapper(mpSelYear, mpSelMonth, 1);
                update(d);
                focus();
                hideMonthPicker();
            }
        });

        monthPickerCancelButton = new TextButton(getMessages().cancelText());
        monthPickerCancelButton.addSelectHandler(new SelectHandler() {
            @Override
            public void onSelect(SelectEvent event) {
                focus();
                hideMonthPicker();
            }
        });

        monthPicker = XElement.createElement("div");
        monthPicker.getStyle().setDisplay(Display.NONE);

        getElement().appendChild(monthPicker);

        cells = Util.toElementArray(appearance.getDateCells(getElement()));

        activeDate = value != null ? value : new DateWrapper();
        update(activeDate);

        new KeyNav(this) {
            @Override
            public void onKeyPress(NativeEvent evt) {
                handlerKeyPress(evt);
            }
        };

        getElement().makePositionable();
        getElement().setTabIndex(0);
        getElement().setAttribute("hideFocus", "true");
        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.ONFOCUS);

        XElement target = getElement().selectNode(appearance.leftMonthSelector());
        target.setTitle(messages.prevText());
        target = getElement().selectNode(appearance.rightMonthSelector());
        target.setTitle(messages.nextText());
        target = getElement().selectNode(appearance.monthButtonSelector());
        target.setTitle(messages.monthYearText());

        addGestureRecognizer(new TapGestureRecognizer() {

            @Override
            protected void onTap(TouchData touchData) {
                super.onTap(touchData);
                DatePicker.this.onTap(touchData);
            }
        });
    }

    @Override
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Date> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    public DatePickerAppearance getAppearance() {
        return appearance;
    }

    /**
     * Returns the field's maximum allowed date.
     *
     * @return the max date
     */
    public Date getMaxDate() {
        return maxDate;
    }

    /**
     * Sets the picker's maximum allowed date.
     *
     * @param maxDate the max date
     */
    public void setMaxDate(Date maxDate) {
        if (maxDate != null) {
            maxDate = new DateWrapper(maxDate).resetTime().asDate();
        }
        this.maxDate = maxDate;
        update(activeDate);
        enableTodayButton();
    }

    /**
     * Returns the date picker messages.
     *
     * @return the messages
     */
    public DatePickerMessages getMessages() {
        if (messages == null) {
            messages = new DatePickerDefaultMessages();
        }
        return messages;
    }

    /**
     * Optionally, sets the date picker messages.
     *
     * @param messages the messages
     */
    public void setMessages(DatePickerMessages messages) {
        this.messages = messages;
    }

    /**
     * Returns the date time messages used by the date picker. Changes to the returned instance (calling setters) will not be applied to the date picker until {@link #setDateTimeInfo(com.sencha.gxt.widget.core.client.DatePicker.DatePickerDateTimeFormatInfo)} is called.
     *
     * @return the date time info
     */
    public DatePickerDateTimeFormatInfo getDateTimeInfo() {
        return dateTimeInfo;
    }

    /**
     * Sets the date time info instance and updates its values. This method allows the date picker's constants to be changed at runtime.
     *
     * @param dateTimeInfo the date time info
     */
    public void setDateTimeInfo(DatePickerDateTimeFormatInfo dateTimeInfo) {
        this.dateTimeInfo = dateTimeInfo;
        setStartDay(this.dateTimeInfo.getFirstDayOfTheWeek());
    }

    /**
     * Returns the picker's minimum data.
     *
     * @return the minimum date
     */
    public Date getMinDate() {
        return minDate;
    }

    /**
     * Sets the picker's minimum allowed date.
     *
     * @param minDate the minimum date
     */
    public void setMinDate(Date minDate) {
        if (minDate != null) {
            minDate = new DateWrapper(minDate).resetTime().asDate();
        }
        this.minDate = minDate;
        update(activeDate);
        enableTodayButton();
    }

    /**
     * Returns the picker's start day.
     *
     * @return the start day
     */
    public int getStartDay() {
        return startDay;
    }

    /**
     * Sets the picker's start day index as returned by {@link DateTimeFormatInfo#weekdaysNarrow()}.
     *
     * @param startDay the start day index
     */
    public void setStartDay(int startDay) {
        this.startDay = startDay;

        String[] dn = dateTimeInfo.getWeekdaysNarrow();

        List<SafeHtml> days = new ArrayList<SafeHtml>();
        for (int i = 0; i < 7; i++) {
            days.add(SafeHtmlUtils.fromString(dn[(i + startDay) % 7]));
        }

        appearance.onUpdateDayOfWeeks(getElement(), days);

        update(activeDate);
    }

    @Override
    public Date getValue() {
        return value != null ? value.asDate() : null;
    }

    @Override
    public void setValue(Date date) {
        setValue(date, true);
    }

    @Override
    public void onBrowserEvent(Event event) {
        super.onBrowserEvent(event);
        if (!isEnabled()) {
            return;
        }
        switch (event.getTypeInt()) {
        case Event.ONCLICK:
            onClick(event);
            break;
        case Event.ONMOUSEOVER:
            onMouseOver(event);
            break;
        case Event.ONMOUSEOUT:
            onMouseOut(event);
            break;
        case Event.ONFOCUS:
            onFocus(event);
            break;
        }
    }

    @Override
    protected void onEnable() {
        super.onEnable();
        todayBtn.enable();
    }

    @Override
    protected void onDisable() {
        super.onDisable();
        todayBtn.disable();
    }

    @Override
    public void setValue(Date date, boolean fireEvents) {
        if (date == null) {
            this.value = null;
            update(new DateWrapper().resetTime());
        } else {
            this.value = new DateWrapper(date).resetTime();
            update(value);
        }

        if (overElement != null) {
            appearance.onUpdateDateStyle(overElement, DateState.OVER, false);
        }

        if (fireEvents) {
            ValueChangeEvent.fire(this, date);
        }
    }

    @Override
    protected void doAttachChildren() {
        super.doAttachChildren();
        ComponentHelper.doAttach(todayBtn);
        ComponentHelper.doAttach(monthPickerOkButton);
        ComponentHelper.doAttach(monthPickerCancelButton);
    }

    @Override
    protected void doDetachChildren() {
        super.doDetachChildren();
        ComponentHelper.doDetach(todayBtn);
        ComponentHelper.doDetach(monthPickerOkButton);
        ComponentHelper.doDetach(monthPickerCancelButton);
    }

    protected int getCalculatedStartDay() {
        return startDay != Integer.MIN_VALUE ? startDay : dateTimeInfo.getFirstDayOfTheWeek();
    }

    protected void handlerKeyPress(NativeEvent evt) {
        if (!isEnabled()) {
            return;
        }
        switch (evt.getKeyCode()) {
        case KeyCodes.KEY_DOWN:
            onKeyDown(evt);
            break;
        case KeyCodes.KEY_END:
            onKeyEnd(evt);
            break;
        case KeyCodes.KEY_ENTER:
            onKeyEnter(evt);
            break;
        case KeyCodes.KEY_HOME:
            onKeyHome(evt);
            break;
        case KeyCodes.KEY_LEFT:
            onKeyLeft(evt);
            break;
        case KeyCodes.KEY_PAGEDOWN:
            onKeyPageDown(evt);
            break;

        case KeyCodes.KEY_PAGEUP:
            onKeyPageUp(evt);
            break;

        case KeyCodes.KEY_RIGHT:
            onKeyRight(evt);
            break;

        case KeyCodes.KEY_UP:
            onKeyUp(evt);
            break;
        }

        if (evt.getKeyCode() == 32) {
            EventTarget eventTarget = evt.getEventTarget();
            if (!(Element.is(eventTarget) && todayBtn.getElement().isOrHasChild(Element.as(eventTarget)))) {
                selectToday();
            }
        }
    }

    protected void hideMonthPicker() {
        Fx fx = new Fx();
        fx.addAfterAnimateHandler(new AfterAnimateHandler() {
            @Override
            public void onAfterAnimate(AfterAnimateEvent event) {
                monthPicker.setVisible(false);
            }
        });
        monthPicker.<FxElement>cast().slideOut(Direction.UP, fx);
    }

    protected void onClick(Event event) {
        event.preventDefault();
        XElement target = event.getEventTarget().cast();
        XElement pn = null;

        if ((pn = target.findParent(appearance.daySelector(), 2)) != null) {
            event.preventDefault();
            onDayClick(pn);
        } else if ((pn = target.findParent(appearance.leftMonthSelector(), 1)) != null) {
            showPrevMonth();
        } else if ((pn = target.findParent(appearance.rightMonthSelector(), 1)) != null) {
            showNextMonth();
        } else if ((pn = target.findParent(appearance.monthButtonSelector(), 5)) != null) {
            showMonthPicker();
        }

        if ((pn = target.findParent(appearance.monthPickerMonthSelector(), 2)) != null) {
            for (int i = 0; i < mpMonths.getCount(); i++) {
                appearance.onMonthSelected(mpMonths.getElement(i), false);
            }
            appearance.onMonthSelected(pn, true);
            mpSelMonth = pn.getPropertyInt("xmonth");
        } else if ((pn = target.findParent(appearance.monthPickerYearSelector(), 2)) != null) {
            for (int i = 0; i < mpYears.getCount(); i++) {
                appearance.onMonthSelected(mpYears.getElement(i), false);
            }
            appearance.onMonthSelected(pn, true);
            mpSelYear = pn.getPropertyInt("xyear");
        } else if (target.is(appearance.leftYearSelector())) {
            updateMPYear(mpyear - 10);
        } else if (target.is(appearance.rightYearSelector())) {
            updateMPYear(mpyear + 10);
        }

        if (GXT.isSafari()) {
            focus();
        }
    }

    protected void onDayClick(XElement e) {
        if (e != null) {
            String dt = e.getPropertyString("dateValue");
            if (dt != null) {
                handleDateClick(e, dt);
                return;
            }
        }
    }

    protected void onKeyDown(NativeEvent evt) {
        if (evt.<XEvent>cast().getCtrlOrMetaKey()) {
            showPreviousYear();
        } else {
            setValue(activeDate.addDays(7).asDate(), false);
        }
    }

    protected void onKeyEnd(NativeEvent evt) {
        if (evt.getShiftKey()) {
            setValue(new DateWrapper(activeDate.getFullYear(), 11, 31).asDate());
        } else {
            setValue(activeDate.getLastDateOfMonth().asDate(), false);
        }
    }

    protected void onKeyEnter(NativeEvent evt) {
        evt.preventDefault();
        evt.stopPropagation();
        setValue(activeDate.asDate());
    }

    protected void onKeyHome(NativeEvent evt) {
        if (evt.<XEvent>cast().getCtrlOrMetaKey()) {
            setValue(new DateWrapper(activeDate.getFullYear(), 0, 1).asDate());
        } else {
            setValue(activeDate.getFirstDayOfMonth().asDate(), false);
        }
    }

    protected void onKeyLeft(NativeEvent evt) {
        XEvent e = evt.cast();
        e.stopEvent();
        if (e.getCtrlOrMetaKey()) {
            showPrevMonth();
        } else {
            setValue(activeDate.addDays(-1).asDate(), false);
        }
    }

    protected void onKeyPageDown(NativeEvent evt) {
        if (evt.getShiftKey()) {
            setValue(activeDate.addYears(1).asDate(), false);
        } else {
            setValue(activeDate.addMonths(1).asDate(), false);
        }
    }

    protected void onKeyPageUp(NativeEvent evt) {
        if (evt.getShiftKey()) {
            setValue(activeDate.addYears(-1).asDate(), false);
        } else {
            setValue(activeDate.addMonths(-1).asDate(), false);
        }
    }

    protected void onKeyRight(NativeEvent evt) {
        XEvent e = evt.cast();
        e.stopEvent();
        if (e.getCtrlOrMetaKey()) {
            showNextMonth();
        } else {
            setValue(activeDate.addDays(1).asDate(), false);
        }
    }

    protected void onKeyUp(NativeEvent evt) {
        XEvent e = evt.cast();
        e.stopEvent();
        if (e.getCtrlOrMetaKey()) {
            showNextYear();
        } else {
            setValue(activeDate.addDays(-7).asDate(), false);
        }
    }

    protected void onMouseOut(Event event) {
        XElement ce = event.getEventTarget().cast();

        XElement target = ce.findParent(appearance.dateSelector(), 3);
        if (target != null && target == overElement) {
            overElement = null;
            appearance.onUpdateDateStyle(target, DateState.OVER, false);
        }
    }

    protected void onMouseOver(Event event) {
        XElement ce = event.getEventTarget().cast();

        XElement target = ce.findParent(appearance.dateSelector(), 3);

        if (target != null && overElement != target) {
            overElement = target;
            appearance.onUpdateDateStyle(target, DateState.OVER, true);
        }
    }

    protected void onTap(TouchData touchData) {
        Event touchEvent = touchData.getLastNativeEvent().cast();
        onClick(touchEvent);
    }

    protected void showMonthPicker() {
        createMonthPicker();

        Size s = getElement().getSize(true);
        s.setHeight(s.getHeight() - 2);

        monthPicker.setTop(1);

        appearance.onMonthPickerSize(monthPicker, s.getWidth(), s.getHeight());

        mpSelMonth = (activeDate != null ? activeDate : value).getMonth();

        updateMPMonth(mpSelMonth);
        mpSelYear = (activeDate != null ? activeDate : value).getFullYear();
        updateMPYear(mpSelYear);

        monthPicker.getStyle().setDisplay(Display.BLOCK);
        monthPicker.makePositionable(true);
        monthPicker.<FxElement>cast().slideIn(Direction.DOWN);
    }

    protected void showNextMonth() {
        setValue(activeDate.addMonths(+1).asDate(), false);
    }

    protected void showNextYear() {
        setValue(activeDate.addYears(1).asDate(), false);
    }

    protected void showPreviousYear() {
        setValue(activeDate.addYears(-1).asDate(), false);
    }

    protected void showPrevMonth() {
        setValue(activeDate.addMonths(-1).asDate(), false);
    }

    protected void updateMPMonth(int month) {
        for (int i = 0; i < mpMonths.getCount(); i++) {
            Element elem = mpMonths.item(i);
            int xmonth = elem.getPropertyInt("xmonth");
            appearance.onMonthSelected(elem, xmonth == month);
        }
    }

    protected void updateMPYear(int year) {
        mpyear = year;
        for (int i = 1; i <= 10; i++) {
            XElement td = XElement.as(mpYears.item(i - 1));
            int y2;
            if (i % 2 == 0) {
                y2 = (int) (year + (Math.round(i * .5)));
            } else {
                y2 = (int) (year - (5 - Math.round(i * .5)));
            }
            td.getFirstChildElement().setInnerText("" + y2);
            td.setPropertyInt("xyear", y2);
            appearance.onMonthSelected(td, y2 == mpSelYear);
        }
    }

    protected void createMonthPicker() {
        SafeHtmlBuilder builder = new SafeHtmlBuilder();
        appearance.renderMonthPicker(builder, messages, dateTimeInfo.getMonthsShort());

        monthPicker.removeChildren();
        monthPicker.setInnerSafeHtml(builder.toSafeHtml());

        monthPicker.selectNode(appearance.monthPickerOkSelector()).appendChild(monthPickerOkButton.getElement());
        monthPicker.selectNode(appearance.monthPickerCancelSelector())
                .appendChild(monthPickerCancelButton.getElement());

        mpMonths = new CompositeElement(
                Util.toElementArray(monthPicker.select(appearance.monthPickerMonthSelector())));
        mpYears = new CompositeElement(
                Util.toElementArray(monthPicker.select(appearance.monthPickerYearSelector())));

        mpMonths.each(new CompositeFunction() {

            public void doFunction(Element elem, CompositeElement ce, int index) {
                index += 1;
                if (index % 2 == 0) {
                    elem.setPropertyInt("xmonth", (int) (5 + (Math.round(index * .5))));
                } else {
                    elem.setPropertyInt("xmonth", (int) (Math.round((index - 1) * .5)));
                }
            }

        });
    }

    @SuppressWarnings("deprecation")
    protected void enableTodayButton() {
        boolean minDateDisable = false;
        boolean maxDateDisable = false;

        if (minDate != null) {
            Date endOfToday = new Date();
            CalendarUtil.addDaysToDate(endOfToday, 1);
            endOfToday.setHours(0);
            endOfToday.setMinutes(0);
            endOfToday.setSeconds(0);
            minDateDisable = minDate.after(endOfToday);
        }

        if (maxDate != null) {
            Date endOfYesterday = new Date();
            CalendarUtil.addDaysToDate(endOfYesterday, -1);
            endOfYesterday.setHours(23);
            endOfYesterday.setMinutes(59);
            endOfYesterday.setSeconds(59);
            maxDateDisable = maxDate.before(endOfYesterday);
        }

        if (minDateDisable || maxDateDisable) {
            todayBtn.setEnabled(false);
        } else {
            todayBtn.setEnabled(true);
        }
    }

    protected void handleDateClick(XElement target, String dt) {
        String[] tokens = dt.split(",");
        int year = Integer.parseInt(tokens[0]);
        int month = Integer.parseInt(tokens[1]);
        int day = Integer.parseInt(tokens[2]);
        Date d = new DateWrapper(year, month, day).asDate();
        if (d != null && !appearance.isDisabled(target.getParentElement())) {
            setValue(d);
        }
    }

    protected void selectToday() {
        setValue(new DateWrapper().asDate());
    }

    protected void setCellStyle(Element cell, Date d, long sel, long min, long max) {
        long t = d.getTime();

        DateWrapper w = new DateWrapper(d);
        int year = w.getFullYear();
        int month = w.getMonth();
        int day = w.getDate();

        String dd = year + "," + month + "," + day;

        cell.getFirstChildElement().setPropertyString("dateValue", dd);

        appearance.onUpdateDateStyle(cell, DateState.TODAY, t == today);
        appearance.onUpdateDateStyle(cell, DateState.SELECTED, t == sel);
        appearance.onUpdateDateStyle(cell, DateState.DISABLED, t > max || t < min);

        if (t > max || t < min) {
            if (t > max) {
                cell.setTitle(messages.maxText());
            } else {
                cell.setTitle(messages.minText());
            }
        }
    }

    protected void update(DateWrapper date) {
        DateWrapper vd = activeDate;
        activeDate = date;
        if (vd != null) {
            int days = date.getDaysInMonth();
            DateWrapper firstOfMonth = date.getFirstDayOfMonth();
            int startingPos = firstOfMonth.getDayInWeek() - startDay;

            if (startingPos <= startDay) {
                startingPos += 7;
            }

            // go to previous month.
            DateWrapper pm = activeDate.addMonths(-1);
            int prevStart = pm.getDaysInMonth() - startingPos;

            days += startingPos;

            DateWrapper d = new DateWrapper(pm.getFullYear(), pm.getMonth(), prevStart).resetTime();
            today = new DateWrapper().resetTime().getTime();
            long sel = value != null ? value.resetTime().getTime() : Long.MIN_VALUE;
            long min = minDate != null ? new DateWrapper(minDate).getTime() : Long.MIN_VALUE;
            long max = maxDate != null ? new DateWrapper(maxDate).getTime() : Long.MAX_VALUE;

            int i = 0;
            for (; i < startingPos; i++) {
                appearance.onHtmlChange(cells[i], SafeHtmlUtils.fromTrustedString("" + ++prevStart));
                d = d.addDays(1);
                appearance.onUpdateDateStyle(cells[i], DateState.PREVIOUS, true);
                setCellStyle(cells[i], d.asDate(), sel, min, max);

                cells[i].setTitle(DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM).format(d.asDate()));
            }
            for (; i < days; i++) {
                int intDay = i - startingPos + 1;
                appearance.onHtmlChange(cells[i], SafeHtmlUtils.fromTrustedString("" + intDay));
                d = d.addDays(1);
                appearance.onUpdateDateStyle(cells[i], DateState.PREVIOUS, false);
                appearance.onUpdateDateStyle(cells[i], DateState.NEXT, false);
                appearance.onUpdateDateStyle(cells[i], DateState.ACTIVE, true);
                setCellStyle(cells[i], d.asDate(), sel, min, max);
                cells[i].setTitle(DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM).format(d.asDate()));
            }
            int extraDays = 0;
            for (; i < 42; i++) {
                appearance.onHtmlChange(cells[i], SafeHtmlUtils.fromTrustedString("" + ++extraDays));
                d = d.addDays(1);
                appearance.onUpdateDateStyle(cells[i], DateState.NEXT, true);
                setCellStyle(cells[i], d.asDate(), sel, min, max);
                cells[i].setTitle(DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM).format(d.asDate()));
            }
            int month = activeDate.getMonth();

            SafeHtml t = SafeHtmlUtils
                    .fromString(dateTimeInfo.getMonthsFullStandalone()[month] + " " + activeDate.getFullYear());

            appearance.onMonthButtonHtmlChange(getElement(), t);
        }

    }

}