]> source.dussan.org Git - vaadin-framework.git/commitdiff
Added keyboard navigation to PopupDateField #4506
authorJohn Alhroos <john.ahlroos@itmill.com>
Fri, 11 Jun 2010 13:43:22 +0000 (13:43 +0000)
committerJohn Alhroos <john.ahlroos@itmill.com>
Fri, 11 Jun 2010 13:43:22 +0000 (13:43 +0000)
svn changeset:13650/svn branch:6.4

WebContent/VAADIN/themes/base/datefield/datefield.css
src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java
src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java
src/com/vaadin/terminal/gwt/client/ui/VTime.java

index f1c38513e14ccb5aaf937c7929d2afa7f05bdeef..6f69a91f5e001580c84eeefa09a34ce02bbafa3a 100644 (file)
@@ -22,6 +22,9 @@
        padding: 0;
        margin: 0;
 }
+.v-datefield-calendarpanel:focus{
+       outline:none;   
+}
 .v-datefield-calendarpanel-header td {
        text-align: center;
 }
diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java
new file mode 100644 (file)
index 0000000..a966aa8
--- /dev/null
@@ -0,0 +1,102 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.terminal.gwt.client.Focusable;
+
+/**
+ * Adds keyboard focus to {@link FlexPanel}.
+ */
+public class FocusableFlexTable extends FlexTable implements HasFocusHandlers,
+        HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+    /**
+     * Default constructor.
+     */
+    public FocusableFlexTable() {
+        // make focusable, as we don't need access key magic we don't need to
+        // use FocusImpl.createFocusable
+        getElement().setTabIndex(0);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.
+     * google.gwt.event.dom.client.FocusHandler)
+     */
+    public HandlerRegistration addFocusHandler(FocusHandler handler) {
+        return addDomHandler(handler, FocusEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google
+     * .gwt.event.dom.client.BlurHandler)
+     */
+    public HandlerRegistration addBlurHandler(BlurHandler handler) {
+        return addDomHandler(handler, BlurEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(
+     * com.google.gwt.event.dom.client.KeyDownHandler)
+     */
+    public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+        return addDomHandler(handler, KeyDownEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler
+     * (com.google.gwt.event.dom.client.KeyPressHandler)
+     */
+    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+        return addDomHandler(handler, KeyPressEvent.getType());
+    }
+
+    /**
+     * Sets the keyboard focus to the panel
+     * 
+     * @param focus
+     *            Should the panel have keyboard focus. If true the keyboard
+     *            focus will be moved to the
+     */
+    public void setFocus(boolean focus) {
+        if (focus) {
+            FocusImpl.getFocusImplForPanel().focus(getElement());
+        } else {
+            FocusImpl.getFocusImplForPanel().blur(getElement());
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.terminal.gwt.client.Focusable#focus()
+     */
+    public void focus() {
+        setFocus(true);
+    }
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java
new file mode 100644 (file)
index 0000000..bb8857c
--- /dev/null
@@ -0,0 +1,97 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.terminal.gwt.client.Focusable;
+
+public class FocusableFlowPanel extends FlowPanel implements HasFocusHandlers,
+        HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+    /**
+     * Constructor
+     */
+    public FocusableFlowPanel() {
+        // make focusable, as we don't need access key magic we don't need to
+        // use FocusImpl.createFocusable
+        getElement().setTabIndex(0);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.
+     * google.gwt.event.dom.client.FocusHandler)
+     */
+    public HandlerRegistration addFocusHandler(FocusHandler handler) {
+        return addDomHandler(handler, FocusEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google
+     * .gwt.event.dom.client.BlurHandler)
+     */
+    public HandlerRegistration addBlurHandler(BlurHandler handler) {
+        return addDomHandler(handler, BlurEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(
+     * com.google.gwt.event.dom.client.KeyDownHandler)
+     */
+    public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+        return addDomHandler(handler, KeyDownEvent.getType());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler
+     * (com.google.gwt.event.dom.client.KeyPressHandler)
+     */
+    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+        return addDomHandler(handler, KeyPressEvent.getType());
+    }
+
+    /**
+     * Sets/Removes the keyboard focus to the panel.
+     * 
+     * @param focus
+     *            If set to true then the focus is moved to the panel, if set to
+     *            false the focus is removed
+     */
+    public void setFocus(boolean focus) {
+        if (focus) {
+            FocusImpl.getFocusImplForPanel().focus(getElement());
+        } else {
+            FocusImpl.getFocusImplForPanel().blur(getElement());
+        }
+    }
+
+    /**
+     * Focus the panel
+     */
+    public void focus() {
+        setFocus(true);
+    }
+}
index 0c2cb056a60ab0acdb1bb4adc3bc0c6440d0cb88..bf753ce5307f774ea935971bd73792fa9b324917 100644 (file)
@@ -10,18 +10,177 @@ import java.util.List;
 \r
 import com.google.gwt.event.dom.client.ClickEvent;\r
 import com.google.gwt.event.dom.client.ClickHandler;\r
+import com.google.gwt.event.dom.client.KeyCodes;\r
+import com.google.gwt.event.dom.client.KeyDownEvent;\r
+import com.google.gwt.event.dom.client.KeyDownHandler;\r
+import com.google.gwt.event.dom.client.KeyPressEvent;\r
+import com.google.gwt.event.dom.client.KeyPressHandler;\r
 import com.google.gwt.user.client.DOM;\r
 import com.google.gwt.user.client.Event;\r
 import com.google.gwt.user.client.Timer;\r
 import com.google.gwt.user.client.ui.FlexTable;\r
+import com.google.gwt.user.client.ui.InlineHTML;\r
 import com.google.gwt.user.client.ui.MouseListener;\r
 import com.google.gwt.user.client.ui.MouseListenerCollection;\r
 import com.google.gwt.user.client.ui.SourcesMouseEvents;\r
 import com.google.gwt.user.client.ui.Widget;\r
+import com.vaadin.terminal.gwt.client.BrowserInfo;\r
 import com.vaadin.terminal.gwt.client.DateTimeService;\r
 import com.vaadin.terminal.gwt.client.LocaleService;\r
 \r
-public class VCalendarPanel extends FlexTable implements MouseListener {\r
+public class VCalendarPanel extends FocusableFlexTable implements\r
+        MouseListener, KeyDownHandler, KeyPressHandler {\r
+\r
+    /**\r
+     * Represents a Date button in the calendar\r
+     */\r
+    private class VEventButton extends VNativeButton implements\r
+            SourcesMouseEvents {\r
+\r
+        private MouseListenerCollection mouseListeners;\r
+\r
+        /**\r
+         * Default constructor\r
+         */\r
+        public VEventButton() {\r
+            super();\r
+            sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK\r
+                    | Event.MOUSEEVENTS);\r
+        }\r
+\r
+        /*\r
+         * (non-Javadoc)\r
+         * \r
+         * @see\r
+         * com.google.gwt.user.client.ui.FocusWidget#addMouseListener(com.google\r
+         * .gwt.user.client.ui.MouseListener)\r
+         */\r
+        @Override\r
+        public void addMouseListener(MouseListener listener) {\r
+            if (mouseListeners == null) {\r
+                mouseListeners = new MouseListenerCollection();\r
+            }\r
+            mouseListeners.add(listener);\r
+        }\r
+\r
+        /*\r
+         * (non-Javadoc)\r
+         * \r
+         * @see\r
+         * com.google.gwt.user.client.ui.FocusWidget#removeMouseListener(com\r
+         * .google.gwt.user.client.ui.MouseListener)\r
+         */\r
+        @Override\r
+        public void removeMouseListener(MouseListener listener) {\r
+            if (mouseListeners != null) {\r
+                mouseListeners.remove(listener);\r
+            }\r
+        }\r
+\r
+        /*\r
+         * (non-Javadoc)\r
+         * \r
+         * @see\r
+         * com.vaadin.terminal.gwt.client.ui.VNativeButton#onBrowserEvent(com\r
+         * .google.gwt.user.client.Event)\r
+         */\r
+        @Override\r
+        public void onBrowserEvent(Event event) {\r
+            super.onBrowserEvent(event);\r
+            switch (DOM.eventGetType(event)) {\r
+            case Event.ONMOUSEDOWN:\r
+            case Event.ONMOUSEUP:\r
+            case Event.ONMOUSEOUT:\r
+                if (mouseListeners != null) {\r
+                    mouseListeners.fireMouseEvent(this, event);\r
+                }\r
+                break;\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Represents a click handler for when a user selects a date by using the\r
+     * mouse\r
+     */\r
+    private class DateClickHandler implements ClickHandler {\r
+\r
+        private final VCalendarPanel cal;\r
+\r
+        public DateClickHandler(VCalendarPanel panel) {\r
+            cal = panel;\r
+        }\r
+\r
+        public void selectDate(String date) {\r
+            try {\r
+                final Integer day = new Integer(date);\r
+                final Date newDate = cal.datefield.getShowingDate();\r
+                newDate.setDate(day.intValue());\r
+                if (!isEnabledDate(newDate)) {\r
+                    return;\r
+                }\r
+                if (cal.datefield.getCurrentDate() == null) {\r
+                    cal.datefield.setCurrentDate(new Date(newDate.getTime()));\r
+\r
+                    // Init variables with current time\r
+                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                            "hour", newDate.getHours(), false);\r
+                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                            "min", newDate.getMinutes(), false);\r
+                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                            "sec", newDate.getSeconds(), false);\r
+                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                            "msec", datefield.getMilliseconds(), false);\r
+                }\r
+\r
+                cal.datefield.getCurrentDate().setTime(newDate.getTime());\r
+                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                        "day", cal.datefield.getCurrentDate().getDate(), false);\r
+                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                        "month", cal.datefield.getCurrentDate().getMonth() + 1,\r
+                        false);\r
+                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+                        "year",\r
+                        cal.datefield.getCurrentDate().getYear() + 1900,\r
+                        cal.datefield.isImmediate());\r
+\r
+                if (datefield instanceof VTextualDate) {\r
+                    ((VOverlay) getParent()).hide();\r
+                } else {\r
+                    updateCalendar();\r
+                }\r
+\r
+            } catch (final NumberFormatException e) {\r
+                // Not a number, ignore and stop here\r
+                return;\r
+            }\r
+        }\r
+\r
+        /*\r
+         * (non-Javadoc)\r
+         * \r
+         * @see\r
+         * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt\r
+         * .event.dom.client.ClickEvent)\r
+         */\r
+        public void onClick(ClickEvent event) {\r
+            Object sender = event.getSource();\r
+            Cell cell = cal.days.getCellForEvent(event);\r
+            if (sender != cal.days || cell == null || cell.getRowIndex() < 1\r
+                    || cell.getRowIndex() > 6 || !cal.datefield.isEnabled()\r
+                    || cal.datefield.isReadonly() || cell.getCellIndex() < 1) {\r
+                return;\r
+            }\r
+\r
+            final String text = cal.days.getText(cell.getRowIndex(), cell\r
+                    .getCellIndex());\r
+            if (text.equals(" ")) {\r
+                return;\r
+            }\r
+\r
+            selectDate(text);\r
+        }\r
+    }\r
 \r
     private final VDateField datefield;\r
 \r
@@ -49,18 +208,57 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
     /* Needed to identify locale changes */\r
     private String locale = LocaleService.getDefaultLocale();\r
 \r
+    private int selectedRow;\r
+    private int selectedColumn;\r
+\r
+    private boolean changingView = false;\r
+\r
+    private final DateClickHandler dateClickHandler;\r
+\r
+    private Timer mouseTimer;\r
+\r
     public VCalendarPanel(VDateField parent) {\r
+        super();\r
+\r
         datefield = parent;\r
         setStyleName(VDateField.CLASSNAME + "-calendarpanel");\r
         // buildCalendar(true);\r
-        days.addClickHandler(new DateClickHandler(this));\r
+\r
+        dateClickHandler = new DateClickHandler(this);\r
+        days.addClickHandler(dateClickHandler);\r
+\r
+        /*\r
+         * Firefox auto-repeat works correctly only if we use a key press\r
+         * handler, other browsers handle it correctly when using a key down\r
+         * handler\r
+         */\r
+        if (BrowserInfo.get().isGecko()) {\r
+            addKeyPressHandler(this);\r
+        } else {\r
+            addKeyDownHandler(this);\r
+        }\r
+\r
     }\r
 \r
     public VCalendarPanel(VDateField parent, Date min, Date max) {\r
+        super();\r
+\r
         datefield = parent;\r
         setStyleName(VDateField.CLASSNAME + "-calendarpanel");\r
-        days.addClickHandler(new DateClickHandler(this));\r
 \r
+        dateClickHandler = new DateClickHandler(this);\r
+        days.addClickHandler(dateClickHandler);\r
+\r
+        /*\r
+         * Firefox auto-repeat works correctly only if we use a key press\r
+         * handler, other browsers handle it correctly when using a key down\r
+         * handler\r
+         */\r
+        if (BrowserInfo.get().isGecko()) {\r
+            addKeyPressHandler(this);\r
+        } else {\r
+            addKeyDownHandler(this);\r
+        }\r
     }\r
 \r
     private void buildCalendar(boolean forceRedraw) {\r
@@ -118,9 +316,11 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
                 prevYear = new VEventButton();\r
                 prevYear.setHTML("&laquo;");\r
                 prevYear.setStyleName("v-button-prevyear");\r
+                prevYear.getElement().setTabIndex(-1);\r
                 nextYear = new VEventButton();\r
                 nextYear.setHTML("&raquo;");\r
                 nextYear.setStyleName("v-button-nextyear");\r
+                nextYear.getElement().setTabIndex(-1);\r
                 prevYear.addMouseListener(this);\r
                 nextYear.addMouseListener(this);\r
                 setWidget(0, 0, prevYear);\r
@@ -130,9 +330,11 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
                     prevMonth = new VEventButton();\r
                     prevMonth.setHTML("&lsaquo;");\r
                     prevMonth.setStyleName("v-button-prevmonth");\r
+                    prevMonth.getElement().setTabIndex(-1);\r
                     nextMonth = new VEventButton();\r
                     nextMonth.setHTML("&rsaquo;");\r
                     nextMonth.setStyleName("v-button-nextmonth");\r
+                    nextMonth.getElement().setTabIndex(-1);\r
                     prevMonth.addMouseListener(this);\r
                     nextMonth.addMouseListener(this);\r
                     setWidget(0, 3, nextMonth);\r
@@ -217,10 +419,17 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
 \r
         // date actually selected?\r
         Date selectedDate = datefield.getCurrentDate();\r
+\r
         // Showing is the date (year/month+year) that is currently shown in the\r
         // panel\r
         Date showing = datefield.getShowingDate();\r
 \r
+        // Always show something\r
+        if (showing == null) {\r
+            showing = new Date();\r
+            datefield.setShowingDate(showing);\r
+        }\r
+\r
         // The day of month that is selected, -1 if no day of this month is\r
         // selected (i.e, showing another month/year than selected or nothing is\r
         // selected)\r
@@ -230,7 +439,9 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
         int dayOfMonthToday = -1;\r
 \r
         // Find out a day this month is selected\r
-        if (selectedDate != null\r
+        if (showing != null) {\r
+            dayOfMonthSelected = showing.getDate();\r
+        } else if (selectedDate != null\r
                 && selectedDate.getMonth() == showing.getMonth()\r
                 && selectedDate.getYear() == showing.getYear()) {\r
             dayOfMonthSelected = selectedDate.getDate();\r
@@ -284,28 +495,31 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
                         }\r
                     }\r
 \r
-                    // Add CSS classes according to state\r
-                    String cssClass = baseclass;\r
+                    // Actually write the day of month\r
+                    InlineHTML html = new InlineHTML(String.valueOf(dayOfMonth));\r
+                    html.setStylePrimaryName(baseclass);\r
+                    html.setTitle(title);\r
 \r
+                    // Add CSS classes according to state\r
                     if (!isEnabledDate(curr)) {\r
-                        cssClass += " " + baseclass + "-disabled";\r
+                        html.addStyleDependentName("disabled");\r
                     }\r
 \r
                     if (dayOfMonthSelected == dayOfMonth) {\r
-                        cssClass += " " + baseclass + "-selected";\r
+                        html.addStyleDependentName("selected");\r
+                        selectedRow = weekOfMonth;\r
+                        selectedColumn = firstWeekdayColumn + dayOfWeek;\r
                     }\r
 \r
                     if (dayOfMonthToday == dayOfMonth) {\r
-                        cssClass += " " + baseclass + "-today";\r
+                        html.addStyleDependentName("today");\r
                     }\r
                     if (title.length() > 0) {\r
-                        cssClass += " " + baseclass + "-entry";\r
+                        html.addStyleDependentName("entry");\r
                     }\r
 \r
-                    // Actually write the day of month\r
-                    days.setHTML(weekOfMonth, firstWeekdayColumn + dayOfWeek,\r
-                            "<span title=\"" + title + "\" class=\"" + cssClass\r
-                                    + "\">" + dayOfMonth + "</span>");\r
+                    days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek,\r
+                            html);\r
 \r
                     // ISO week numbers if requested\r
                     if (!weekNumberProcessed[weekOfMonth]) {\r
@@ -334,7 +548,7 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
 \r
     private void buildTime(boolean forceRedraw) {\r
         if (time == null) {\r
-            time = new VTime(datefield);\r
+            time = new VTime(datefield, this);\r
             setWidget(2, 0, time);\r
             getFlexCellFormatter().setColSpan(2, 0, 5);\r
             getFlexCellFormatter().setStyleName(2, 0,\r
@@ -344,19 +558,39 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
     }\r
 \r
     /**\r
-     * \r
-     * @param forceRedraw\r
-     *            Build all from scratch, in case of e.g. locale changes\r
+     * Updates the calendar and text field with the selected dates *\r
      */\r
     public void updateCalendar() {\r
-        // Locale and resolution changes force a complete redraw\r
-        buildCalendar(locale != datefield.getCurrentLocale()\r
-                || resolution != datefield.getCurrentResolution());\r
+        updateCalendarOnly();\r
         if (datefield instanceof VTextualDate) {\r
             ((VTextualDate) datefield).buildDate();\r
         }\r
-        locale = datefield.getCurrentLocale();\r
-        resolution = datefield.getCurrentResolution();\r
+    }\r
+\r
+    /**\r
+     * Updates the popup only, does not affect the text field\r
+     */\r
+    private void updateCalendarOnly() {\r
+        if (!changingView) {\r
+            changingView = true;\r
+            // Locale and resolution changes force a complete redraw\r
+            buildCalendar(locale != datefield.getCurrentLocale()\r
+                    || resolution != datefield.getCurrentResolution());\r
+            locale = datefield.getCurrentLocale();\r
+            resolution = datefield.getCurrentResolution();\r
+\r
+            /*\r
+             * Wait a while before releasing the lock, so auto-repeated events\r
+             * isn't processed after the calendar is updated\r
+             */\r
+            Timer changeTimer = new Timer() {\r
+                @Override\r
+                public void run() {\r
+                    changingView = false;\r
+                }\r
+            };\r
+            changeTimer.schedule(100);\r
+        }\r
     }\r
 \r
     private boolean isEnabledDate(Date date) {\r
@@ -367,72 +601,123 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
         return true;\r
     }\r
 \r
+    /**\r
+     * Selects the next month\r
+     */\r
+    private void selectNextMonth() {\r
+        Date showingDate = datefield.getShowingDate();\r
+        int currentMonth = showingDate.getMonth();\r
+        showingDate.setMonth(currentMonth + 1);\r
+        int requestedMonth = (currentMonth + 1) % 12;\r
+\r
+        /*\r
+         * If the selected date was e.g. 31.3 the new date would be 31.4 but\r
+         * this date is invalid so the new date will be 1.5. This is taken care\r
+         * of by decreasing the date until we have the correct month.\r
+         */\r
+        while (showingDate.getMonth() != requestedMonth) {\r
+            showingDate.setDate(showingDate.getDate() - 1);\r
+        }\r
+\r
+        updateCalendar();\r
+    }\r
+\r
+    /**\r
+     * Selects the previous month\r
+     */\r
+    private void selectPreviousMonth() {\r
+        Date showingDate = datefield.getShowingDate();\r
+        int currentMonth = showingDate.getMonth();\r
+        showingDate.setMonth(currentMonth - 1);\r
+\r
+        /*\r
+         * If the selected date was e.g. 31.12 the new date would be 31.11 but\r
+         * this date is invalid so the new date will be 1.12. This is taken care\r
+         * of by decreasing the date until we have the correct month.\r
+         */\r
+        while (showingDate.getMonth() == currentMonth) {\r
+            showingDate.setDate(showingDate.getDate() - 1);\r
+        }\r
+\r
+        updateCalendar();\r
+    }\r
+\r
+    /**\r
+     * Selects the previous year\r
+     */\r
+    private void selectPreviousYear(int years) {\r
+        Date showingDate = datefield.getShowingDate();\r
+        showingDate.setYear(showingDate.getYear() - years);\r
+        updateCalendar();\r
+    }\r
+\r
+    /**\r
+     * Selects the next year\r
+     */\r
+    private void selectNextYear(int years) {\r
+        Date showingDate = datefield.getShowingDate();\r
+        showingDate.setYear(showingDate.getYear() + years);\r
+        updateCalendar();\r
+    }\r
+\r
+    /**\r
+     * Handles a user click on the component\r
+     * \r
+     * @param sender\r
+     *            The component that was clicked\r
+     * @param updateVariable\r
+     *            Should the date field be updated\r
+     * \r
+     */\r
     private void processClickEvent(Widget sender, boolean updateVariable) {\r
         if (!datefield.isEnabled() || datefield.isReadonly()) {\r
             return;\r
         }\r
-        Date showingDate = datefield.getShowingDate();\r
         if (!updateVariable) {\r
             if (sender == prevYear) {\r
-                showingDate.setYear(showingDate.getYear() - 1);\r
-                updateCalendar();\r
+                selectPreviousYear(1);\r
             } else if (sender == nextYear) {\r
-                showingDate.setYear(showingDate.getYear() + 1);\r
-                updateCalendar();\r
+                selectNextYear(1);\r
             } else if (sender == prevMonth) {\r
-                int currentMonth = showingDate.getMonth();\r
-                showingDate.setMonth(currentMonth - 1);\r
-\r
-                /*\r
-                 * If the selected date was e.g. 31.12 the new date would be\r
-                 * 31.11 but this date is invalid so the new date will be 1.12.\r
-                 * This is taken care of by decreasing the date until we have\r
-                 * the correct month.\r
-                 */\r
-                while (showingDate.getMonth() == currentMonth) {\r
-                    showingDate.setDate(showingDate.getDate() - 1);\r
-                }\r
-\r
-                updateCalendar();\r
+                selectPreviousMonth();\r
             } else if (sender == nextMonth) {\r
-                int currentMonth = showingDate.getMonth();\r
-                showingDate.setMonth(currentMonth + 1);\r
-                int requestedMonth = (currentMonth + 1) % 12;\r
-\r
-                /*\r
-                 * If the selected date was e.g. 31.3 the new date would be 31.4\r
-                 * but this date is invalid so the new date will be 1.5. This is\r
-                 * taken care of by decreasing the date until we have the\r
-                 * correct month.\r
-                 */\r
-                while (showingDate.getMonth() != requestedMonth) {\r
-                    showingDate.setDate(showingDate.getDate() - 1);\r
-                }\r
-\r
-                updateCalendar();\r
+                selectNextMonth();\r
             }\r
         } else {\r
             if (datefield.getCurrentResolution() == VDateField.RESOLUTION_YEAR\r
                     || datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {\r
-                // Due to current UI, update variable if res=year/month\r
-                datefield.setCurrentDate(new Date(showingDate.getTime()));\r
-                if (datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {\r
-                    datefield.getClient().updateVariable(datefield.getId(),\r
-                            "month", datefield.getCurrentDate().getMonth() + 1,\r
-                            false);\r
-                }\r
-                datefield.getClient().updateVariable(datefield.getId(), "year",\r
-                        datefield.getCurrentDate().getYear() + 1900,\r
-                        datefield.isImmediate());\r
-\r
-                /* Must update the value in the textfield also */\r
-                updateCalendar();\r
+                notifyServerOfChanges();\r
             }\r
         }\r
     }\r
 \r
-    private Timer timer;\r
+    /**\r
+     * Send the date to the server\r
+     */\r
+    private void notifyServerOfChanges() {\r
+        Date showingDate = datefield.getShowingDate();\r
+\r
+        // Due to current UI, update variable if res=year/month\r
+        datefield.setCurrentDate(new Date(showingDate.getTime()));\r
+        if (datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {\r
+            datefield.getClient().updateVariable(datefield.getId(), "month",\r
+                    datefield.getCurrentDate().getMonth() + 1, false);\r
+        }\r
+        datefield.getClient().updateVariable(datefield.getId(), "year",\r
+                datefield.getCurrentDate().getYear() + 1900,\r
+                datefield.isImmediate());\r
+\r
+        /* Must update the value in the textfield also */\r
+        updateCalendar();\r
+    }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.MouseListener#onMouseDown(com.google.gwt\r
+     * .user.client.ui.Widget, int, int)\r
+     */\r
     public void onMouseDown(final Widget sender, int x, int y) {\r
         // Allow user to click-n-hold for fast-forward or fast-rewind.\r
         // Timer is first used for a 500ms delay after mousedown. After that has\r
@@ -440,192 +725,740 @@ public class VCalendarPanel extends FlexTable implements MouseListener {
         // timers are cancelled on mouseup or mouseout.\r
         if (sender instanceof VEventButton) {\r
             processClickEvent(sender, false);\r
-            timer = new Timer() {\r
+            mouseTimer = new Timer() {\r
                 @Override\r
                 public void run() {\r
-                    timer = new Timer() {\r
+                    mouseTimer = new Timer() {\r
                         @Override\r
                         public void run() {\r
                             processClickEvent(sender, false);\r
                         }\r
                     };\r
-                    timer.scheduleRepeating(150);\r
+                    mouseTimer.scheduleRepeating(150);\r
                 }\r
             };\r
-            timer.schedule(500);\r
+            mouseTimer.schedule(500);\r
         }\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.MouseListener#onMouseEnter(com.google.gwt\r
+     * .user.client.ui.Widget)\r
+     */\r
     public void onMouseEnter(Widget sender) {\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.MouseListener#onMouseLeave(com.google.gwt\r
+     * .user.client.ui.Widget)\r
+     */\r
     public void onMouseLeave(Widget sender) {\r
-        if (timer != null) {\r
-            timer.cancel();\r
+        if (mouseTimer != null) {\r
+            mouseTimer.cancel();\r
         }\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.MouseListener#onMouseMove(com.google.gwt\r
+     * .user.client.ui.Widget, int, int)\r
+     */\r
     public void onMouseMove(Widget sender, int x, int y) {\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.MouseListener#onMouseUp(com.google.gwt.\r
+     * user.client.ui.Widget, int, int)\r
+     */\r
     public void onMouseUp(Widget sender, int x, int y) {\r
-        if (timer != null) {\r
-            timer.cancel();\r
+        if (mouseTimer != null) {\r
+            mouseTimer.cancel();\r
         }\r
         processClickEvent(sender, true);\r
     }\r
 \r
-    private class VEventButton extends VNativeButton implements\r
-            SourcesMouseEvents {\r
 \r
-        private MouseListenerCollection mouseListeners;\r
+    public void setLimits(Date min, Date max) {\r
+        if (min != null) {\r
+            final Date d = new Date(min.getTime());\r
+            d.setHours(0);\r
+            d.setMinutes(0);\r
+            d.setSeconds(1);\r
+            minDate = d;\r
+        } else {\r
+            minDate = null;\r
+        }\r
+        if (max != null) {\r
+            final Date d = new Date(max.getTime());\r
+            d.setHours(24);\r
+            d.setMinutes(59);\r
+            d.setSeconds(59);\r
+            maxDate = d;\r
+        } else {\r
+            maxDate = null;\r
+        }\r
+    }\r
 \r
-        public VEventButton() {\r
-            super();\r
-            sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK\r
-                    | Event.MOUSEEVENTS);\r
+    public void setCalendarEntrySource(CalendarEntrySource entrySource) {\r
+        this.entrySource = entrySource;\r
+    }\r
+\r
+    public CalendarEntrySource getCalendarEntrySource() {\r
+        return entrySource;\r
+    }\r
+\r
+    public interface CalendarEntrySource {\r
+        public List getEntries(Date date, int resolution);\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt\r
+     * .event.dom.client.KeyDownEvent)\r
+     */\r
+    public void onKeyDown(KeyDownEvent event) {\r
+        int keycode = event.getNativeKeyCode();\r
+\r
+        if (handleNavigation(keycode, event\r
+                .isControlKeyDown()\r
+                || event.isMetaKeyDown(), event.isShiftKeyDown())) {\r
+            event.preventDefault();\r
         }\r
+    }\r
 \r
-        @Override\r
-        public void addMouseListener(MouseListener listener) {\r
-            if (mouseListeners == null) {\r
-                mouseListeners = new MouseListenerCollection();\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google\r
+     * .gwt.event.dom.client.KeyPressEvent)\r
+     */\r
+    public void onKeyPress(KeyPressEvent event) {\r
+        if (handleNavigation(event.getNativeEvent().getKeyCode(), event\r
+                .isControlKeyDown()\r
+                || event.isMetaKeyDown(), event.isShiftKeyDown())) {\r
+            event.preventDefault();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Get the first valid weekday column index of this month\r
+     * \r
+     * @return A column index or zero if not found\r
+     */\r
+    private int getFirstWeekdayColumn() {\r
+        Widget day = null;\r
+        for (int col = 1; col <= 7; col++) {\r
+            day = days.getWidget(1, col);\r
+            if (day != null) {\r
+                return col;\r
             }\r
-            mouseListeners.add(listener);\r
         }\r
+        return 0;\r
+    }\r
 \r
-        @Override\r
-        public void removeMouseListener(MouseListener listener) {\r
-            if (mouseListeners != null) {\r
-                mouseListeners.remove(listener);\r
+    /**\r
+     * Get the last valid weekday column index of this month\r
+     * \r
+     * @return A column index or zero if not found\r
+     */\r
+    private int[] getLastWeekdayColumn() {\r
+        Widget day = null;\r
+        for (int row = days.getRowCount() - 1; row >= 1; row--) {\r
+            for (int col = 7; col >= 1; col--) {\r
+                day = days.getWidget(row, col);\r
+                if (day != null) {\r
+                    return new int[] { row, col };\r
+                }\r
             }\r
         }\r
+        return new int[] { 0, 0 };\r
+    }\r
 \r
-        @Override\r
-        public void onBrowserEvent(Event event) {\r
-            super.onBrowserEvent(event);\r
-            switch (DOM.eventGetType(event)) {\r
-            case Event.ONMOUSEDOWN:\r
-            case Event.ONMOUSEUP:\r
-            case Event.ONMOUSEOUT:\r
-                if (mouseListeners != null) {\r
-                    mouseListeners.fireMouseEvent(this, event);\r
+    /**\r
+     * Handles the keyboard navigation when the resolution is set to years.\r
+     * \r
+     * @param keycode\r
+     *            The keycode to process\r
+     * @param ctrl\r
+     *            Is ctrl pressed?\r
+     * @param shift\r
+     *            is shift pressed\r
+     * @return Returns true if the keycode was processed, else false\r
+     */\r
+    protected boolean handleNavigationYearMode(int keycode, boolean ctrl,\r
+            boolean shift) {\r
+\r
+        // Ctrl and Shift selection not supported\r
+        if (ctrl || shift) {\r
+            return false;\r
+        }\r
+\r
+        else if (keycode == getPreviousKey()) {\r
+            selectNextYear(10); // Add 10 years\r
+            return true;\r
+        }\r
+\r
+        else if (keycode == getForwardKey()) {\r
+            selectNextYear(1); // Add 1 year\r
+            return true;\r
+        }\r
+\r
+        else if (keycode == getNextKey()) {\r
+            selectPreviousYear(10); // Subtract 10 years\r
+            return true;\r
+        }\r
+\r
+        else if (keycode == getBackwardKey()) {\r
+            selectPreviousYear(1); // Subtract 1 year\r
+            return true;\r
+\r
+        } else if (keycode == getSelectKey()) {\r
+\r
+            // We need to notify the clickhandler of the selection\r
+            dateClickHandler.selectDate(String.valueOf(datefield\r
+                    .getShowingDate().getDay()));\r
+\r
+            // Send changes to server\r
+            notifyServerOfChanges();\r
+\r
+            if (datefield instanceof VTextualDate) {\r
+                ((VTextualDate) datefield).focus();\r
+            }\r
+            return true;\r
+\r
+        } else if (keycode == getResetKey()) {\r
+            // Restore showing date the selected date\r
+            Date showing = datefield.getShowingDate();\r
+            Date current = datefield.getCurrentDate();\r
+            showing.setTime(current.getTime());\r
+            updateCalendar();\r
+            return true;\r
+\r
+        } else if (keycode == getCloseKey()) {\r
+            if (datefield instanceof VPopupCalendar) {\r
+\r
+                // Restore showing date the selected date\r
+                Date showing = datefield.getShowingDate();\r
+                Date current = datefield.getCurrentDate();\r
+\r
+                if (current != null && current != null) {\r
+                    showing.setTime(current.getTime());\r
                 }\r
-                break;\r
+\r
+                // Close popup..\r
+                ((VPopupCalendar) datefield).closeCalendarPanel();\r
+\r
+                // ..and focus the textfield\r
+                ((VPopupCalendar) datefield).focus();\r
             }\r
+\r
+            return true;\r
         }\r
+        return false;\r
     }\r
 \r
-    private class DateClickHandler implements ClickHandler {\r
+    /**\r
+     * Handle the keyboard navigation when the resolution is set to MONTH\r
+     * \r
+     * @param keycode\r
+     *            The keycode to handle\r
+     * @param ctrl\r
+     *            Was the ctrl key pressed?\r
+     * @param shift\r
+     *            Was the shift key pressed?\r
+     * @return\r
+     */\r
+    protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,\r
+            boolean shift) {\r
 \r
-        private final VCalendarPanel cal;\r
+        // Ctrl selection not supported\r
+        if (ctrl) {\r
+            return false;\r
 \r
-        public DateClickHandler(VCalendarPanel panel) {\r
-            cal = panel;\r
+        } else if (keycode == getPreviousKey()) {\r
+            selectNextYear(1); // Add 1 year\r
+            return true;\r
+\r
+        } else if (keycode == getForwardKey()) {\r
+            selectNextMonth(); // Add 1 month\r
+            return true;\r
+\r
+        } else if (keycode == getNextKey()) {\r
+            selectPreviousYear(1); // Subtract 1 year\r
+            return true;\r
+\r
+        } else if (keycode == getBackwardKey()) {\r
+            selectPreviousMonth(); // Subtract 1 month\r
+            return true;\r
+\r
+        } else if (keycode == getSelectKey()) {\r
+\r
+            // We need to notify the clickhandler of the selection\r
+            dateClickHandler.selectDate(String.valueOf(datefield\r
+                    .getShowingDate().getDay()));\r
+\r
+            // Send changes to server\r
+            notifyServerOfChanges();\r
+\r
+            if (datefield instanceof VTextualDate) {\r
+                ((VTextualDate) datefield).focus();\r
+            }\r
+\r
+            return true;\r
+\r
+        } else if (keycode == getResetKey()) {\r
+            // Restore showing date the selected date\r
+            Date showing = datefield.getShowingDate();\r
+            Date current = datefield.getCurrentDate();\r
+            showing.setTime(current.getTime());\r
+            updateCalendar();\r
+            return true;\r
+\r
+        } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {\r
+            if (datefield instanceof VPopupCalendar) {\r
+\r
+                // Restore showing date the selected date\r
+                Date showing = datefield.getShowingDate();\r
+                Date current = datefield.getCurrentDate();\r
+\r
+                if (current != null && current != null) {\r
+                    showing.setTime(current.getTime());\r
+                }\r
+\r
+                // Close popup..\r
+                ((VPopupCalendar) datefield).closeCalendarPanel();\r
+\r
+                // ..and focus the textfield\r
+                ((VPopupCalendar) datefield).focus();\r
+            }\r
+\r
+            return true;\r
         }\r
 \r
-        public void onClick(ClickEvent event) {\r
-            Object sender = event.getSource();\r
-            Cell cell = cal.days.getCellForEvent(event);\r
-            if (sender != cal.days || cell == null || cell.getRowIndex() < 1\r
-                    || cell.getRowIndex() > 6 || !cal.datefield.isEnabled()\r
-                    || cal.datefield.isReadonly() || cell.getCellIndex() < 1) {\r
-                return;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Handle keyboard navigation what the resolution is set to DAY\r
+     * \r
+     * @param keycode\r
+     *            The keycode to handle\r
+     * @param ctrl\r
+     *            Was the ctrl key pressed?\r
+     * @param shift\r
+     *            Was the shift key pressed?\r
+     * @return Return true if the key press was handled by the method, else\r
+     *         return false.\r
+     */\r
+    protected boolean handleNavigationDayMode(int keycode, boolean ctrl,\r
+            boolean shift) {\r
+\r
+        // Ctrl key is not in use\r
+        if (ctrl) {\r
+            return false;\r
+        }\r
+\r
+        Date showingDate = datefield.getShowingDate();\r
+        Widget currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+\r
+        /*\r
+         * Jumps to the next day.\r
+         */\r
+        if (keycode == getForwardKey() && !shift) {\r
+            // Calculate new showing date\r
+            Date newCurrentDate = new Date(showingDate.getYear(), showingDate\r
+                    .getMonth(), showingDate.getDate(), showingDate.getHours(),\r
+                    showingDate.getMinutes(), showingDate.getSeconds());\r
+            newCurrentDate.setDate(newCurrentDate.getDate() + 1);\r
+\r
+            if (newCurrentDate.getMonth() == showingDate.getMonth()) {\r
+                // Month did not change, only move the selection\r
+\r
+                showingDate.setDate(showingDate.getDate() + 1);\r
+\r
+                if (currentSelection != null) {\r
+                    currentSelection.removeStyleDependentName("selected");\r
+\r
+                    // Calculate new selection\r
+                    if (selectedColumn == 7) {\r
+                        selectedColumn = 1;\r
+                        selectedRow++;\r
+                    } else {\r
+                        selectedColumn++;\r
+                    }\r
+                }\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
+\r
+            } else {\r
+                // If the month changed we need to re-render the calendar\r
+                showingDate.setDate(showingDate.getDate() + 1);\r
+                updateCalendarOnly();\r
+\r
+                selectedRow = 1;\r
+                selectedColumn = getFirstWeekdayColumn();\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
             }\r
 \r
-            final String text = cal.days.getText(cell.getRowIndex(), cell\r
-                    .getCellIndex());\r
-            if (text.equals(" ")) {\r
-                return;\r
+            return true;\r
+\r
+            /*\r
+             * Jumps to the previous day\r
+             */\r
+        } else if (keycode == getBackwardKey() && !shift) {\r
+            // Calculate new showing date\r
+            Date newCurrentDate = new Date(showingDate.getYear(), showingDate\r
+                    .getMonth(), showingDate.getDate(), showingDate.getHours(),\r
+                    showingDate.getMinutes(), showingDate.getSeconds());\r
+            newCurrentDate.setDate(newCurrentDate.getDate() - 1);\r
+\r
+            if (newCurrentDate.getMonth() == showingDate.getMonth()) {\r
+                // Month did not change, only move the selection\r
+\r
+                showingDate.setDate(showingDate.getDate() - 1);\r
+\r
+                if (currentSelection != null) {\r
+                    currentSelection.removeStyleDependentName("selected");\r
+\r
+                    // Calculate new selection\r
+                    if (selectedColumn == 1) {\r
+                        selectedColumn = 7;\r
+                        selectedRow--;\r
+                    } else {\r
+                        selectedColumn--;\r
+                    }\r
+\r
+                }\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
+\r
+            } else {\r
+                // If the month changed we need to re-render the calendar\r
+                showingDate.setDate(showingDate.getDate() - 1);\r
+                updateCalendarOnly();\r
+\r
+                int[] pos = getLastWeekdayColumn();\r
+                selectedRow = pos[0];\r
+                selectedColumn = pos[1];\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
             }\r
 \r
-            try {\r
-                final Integer day = new Integer(text);\r
-                final Date newDate = cal.datefield.getShowingDate();\r
-                newDate.setDate(day.intValue());\r
-                if (!isEnabledDate(newDate)) {\r
-                    return;\r
+            return true;\r
+\r
+            /*\r
+             * Jumps one week back in the calendar\r
+             */\r
+        } else if (keycode == getPreviousKey() && !shift) {\r
+            // Calculate new showing date\r
+            Date newCurrentDate = new Date(showingDate.getYear(), showingDate\r
+                    .getMonth(), showingDate.getDate(), showingDate.getHours(),\r
+                    showingDate.getMinutes(), showingDate.getSeconds());\r
+            newCurrentDate.setDate(newCurrentDate.getDate() - 7);\r
+\r
+            if (newCurrentDate.getMonth() == showingDate.getMonth()\r
+                    && selectedRow > 1) {\r
+                // Month did not change, only move the selection\r
+\r
+                showingDate.setDate(showingDate.getDate() - 7);\r
+\r
+                if (currentSelection != null) {\r
+                    currentSelection.removeStyleDependentName("selected");\r
                 }\r
-                if (cal.datefield.getCurrentDate() == null) {\r
-                    cal.datefield.setCurrentDate(new Date(newDate.getTime()));\r
 \r
-                    // Init variables with current time\r
-                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                            "hour", newDate.getHours(), false);\r
-                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                            "min", newDate.getMinutes(), false);\r
-                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                            "sec", newDate.getSeconds(), false);\r
-                    datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                            "msec", datefield.getMilliseconds(), false);\r
+                selectedRow--;\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
+\r
+            } else {\r
+                // If the month changed we need to re-render the calendar\r
+                showingDate.setDate(showingDate.getDate() - 7);\r
+                updateCalendarOnly();\r
+\r
+                int[] pos = getLastWeekdayColumn();\r
+                selectedRow = pos[0];\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                if (currentSelection == null) {\r
+                    selectedRow--;\r
+                    currentSelection = days.getWidget(selectedRow,\r
+                            selectedColumn);\r
                 }\r
 \r
-                cal.datefield.getCurrentDate().setTime(newDate.getTime());\r
-                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                        "day", cal.datefield.getCurrentDate().getDate(), false);\r
-                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                        "month", cal.datefield.getCurrentDate().getMonth() + 1,\r
-                        false);\r
-                cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
-                        "year",\r
-                        cal.datefield.getCurrentDate().getYear() + 1900,\r
-                        cal.datefield.isImmediate());\r
+                currentSelection.addStyleDependentName("selected");\r
+            }\r
 \r
-                if (datefield instanceof VTextualDate\r
-                        && resolution < VDateField.RESOLUTION_HOUR) {\r
-                    ((VOverlay) getParent()).hide();\r
-                } else {\r
-                    updateCalendar();\r
+            return true;\r
+\r
+            /*\r
+             * Jumps one week forward in the calendar\r
+             */\r
+        } else if (keycode == getNextKey() && !ctrl && !shift) {\r
+            // Calculate new showing date\r
+            Date newCurrentDate = new Date(showingDate.getYear(), showingDate\r
+                    .getMonth(), showingDate.getDate(), showingDate.getHours(),\r
+                    showingDate.getMinutes(), showingDate.getSeconds());\r
+            newCurrentDate.setDate(newCurrentDate.getDate() + 7);\r
+\r
+            if (newCurrentDate.getMonth() == showingDate.getMonth()) {\r
+                // Month did not change, only move the selection\r
+\r
+                showingDate.setDate(showingDate.getDate() + 7);\r
+\r
+                if (currentSelection != null) {\r
+                    currentSelection.removeStyleDependentName("selected");\r
                 }\r
 \r
-            } catch (final NumberFormatException e) {\r
-                // Not a number, ignore and stop here\r
-                return;\r
+                selectedRow++;\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                currentSelection.addStyleDependentName("selected");\r
+\r
+            } else {\r
+                // If the month changed we need to re-render the calendar\r
+                showingDate.setDate(showingDate.getDate() + 7);\r
+                updateCalendarOnly();\r
+\r
+                selectedRow = 1;\r
+\r
+                // Set new selection\r
+                currentSelection = days.getWidget(selectedRow, selectedColumn);\r
+                if (currentSelection == null) {\r
+                    selectedRow++;\r
+                    currentSelection = days.getWidget(selectedRow,\r
+                            selectedColumn);\r
+                }\r
+\r
+                currentSelection.addStyleDependentName("selected");\r
             }\r
-        }\r
 \r
+            return true;\r
+\r
+            /*\r
+             * Selects the date that is chosen\r
+             */\r
+        } else if (keycode == getSelectKey() && !shift) {\r
+            InlineHTML selection = (InlineHTML) days.getWidget(selectedRow,\r
+                    selectedColumn);\r
+            dateClickHandler.selectDate(selection.getText());\r
+\r
+            if (datefield instanceof VTextualDate) {\r
+                ((VTextualDate) datefield).focus();\r
+            }\r
+\r
+            return true;\r
+\r
+            /*\r
+             * Closes the date popup\r
+             */\r
+        } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {\r
+            if (datefield instanceof VPopupCalendar) {\r
+\r
+                // Restore showing date the selected date\r
+                Date showing = datefield.getShowingDate();\r
+                Date current = datefield.getCurrentDate();\r
+\r
+                if (current != null && current != null) {\r
+                    showing.setTime(current.getTime());\r
+                }\r
+\r
+                // Close popup..\r
+                ((VPopupCalendar) datefield).closeCalendarPanel();\r
+\r
+                // ..and focus the textfield\r
+                ((VPopupCalendar) datefield).focus();\r
+\r
+            }\r
+\r
+            return true;\r
+\r
+            /*\r
+             * Selects the next month\r
+             */\r
+        } else if (shift && keycode == getForwardKey()) {\r
+            selectNextMonth();\r
+            return true;\r
+\r
+            /*\r
+             * Selects the previous month\r
+             */\r
+        } else if (shift && keycode == getBackwardKey()) {\r
+            selectPreviousMonth();\r
+            return true;\r
+\r
+            /*\r
+             * Selects the next year\r
+             */\r
+        } else if (shift && keycode == getPreviousKey()) {\r
+            selectNextYear(1);\r
+            return true;\r
+\r
+            /*\r
+             * Selects the previous year\r
+             */\r
+        } else if (shift && keycode == getNextKey()) {\r
+            selectPreviousYear(1);\r
+            return true;\r
+\r
+            /*\r
+             * Resets the selection\r
+             */\r
+        } else if (keycode == getResetKey() && !shift) {\r
+            // Restore showing date the selected date\r
+            Date showing = datefield.getShowingDate();\r
+            Date current = datefield.getCurrentDate();\r
+            showing.setTime(current.getTime());\r
+            updateCalendar();\r
+            return true;\r
+        }\r
+        \r
+        return false;\r
     }\r
 \r
-    public void setLimits(Date min, Date max) {\r
-        if (min != null) {\r
-            final Date d = new Date(min.getTime());\r
-            d.setHours(0);\r
-            d.setMinutes(0);\r
-            d.setSeconds(1);\r
-            minDate = d;\r
-        } else {\r
-            minDate = null;\r
+    /**\r
+     * Handles the keyboard navigation\r
+     * \r
+     * @param keycode\r
+     *            The key code that was pressed\r
+     * @param ctrl\r
+     *            Was the ctrl key pressed\r
+     * @param shift\r
+     *            Was the shift key pressed\r
+     * @return Return true if key press was handled by the component, else\r
+     *         return false\r
+     */\r
+    @SuppressWarnings("deprecation")\r
+    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {\r
+        if (!datefield.isEnabled() || datefield.isReadonly() || changingView) {\r
+            return false;\r
         }\r
-        if (max != null) {\r
-            final Date d = new Date(max.getTime());\r
-            d.setHours(24);\r
-            d.setMinutes(59);\r
-            d.setSeconds(59);\r
-            maxDate = d;\r
-        } else {\r
-            maxDate = null;\r
+\r
+        else if (resolution == VDateField.RESOLUTION_YEAR) {\r
+            return handleNavigationYearMode(keycode, ctrl, shift);\r
+        }\r
+\r
+        else if (resolution == VDateField.RESOLUTION_MONTH) {\r
+            return handleNavigationMonthMode(keycode, ctrl, shift);\r
         }\r
+\r
+        else if (resolution == VDateField.RESOLUTION_DAY) {\r
+            return handleNavigationDayMode(keycode, ctrl, shift);\r
+        }\r
+\r
+        else {\r
+            // Do not close the window on tab key but move the\r
+            // focus down to the time\r
+            if (keycode == KeyCodes.KEY_TAB) {\r
+                time.setFocus(true);\r
+                return true;\r
+            }\r
+\r
+            return handleNavigationDayMode(keycode, ctrl, shift);\r
+        }\r
+\r
     }\r
 \r
-    public void setCalendarEntrySource(CalendarEntrySource entrySource) {\r
-        this.entrySource = entrySource;\r
+    /**\r
+     * Returns the reset key which will reset the calendar to the previous\r
+     * selection. By default this is backspace but it can be overriden to change\r
+     * the key to whatever you want.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getResetKey() {\r
+        return KeyCodes.KEY_BACKSPACE;\r
     }\r
 \r
-    public CalendarEntrySource getCalendarEntrySource() {\r
-        return entrySource;\r
+    /**\r
+     * Returns the select key which selects the date. By default this is the\r
+     * enter key but it can be changed to whatever you like by overriding this\r
+     * method.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getSelectKey() {\r
+        return KeyCodes.KEY_ENTER;\r
     }\r
 \r
-    public interface CalendarEntrySource {\r
-        public List getEntries(Date date, int resolution);\r
+    /**\r
+     * Returns the key that closes the popup window if this is a VPopopCalendar.\r
+     * Else this does nothing. By default this is the Escape key but you can\r
+     * change the key to whatever you want by overriding this method.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getCloseKey() {\r
+        return KeyCodes.KEY_ESCAPE;\r
+    }\r
+\r
+    /**\r
+     * The key that selects the next day in the calendar. By default this is the\r
+     * right arrow key but by overriding this method it can be changed to\r
+     * whatever you like.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getForwardKey() {\r
+        return KeyCodes.KEY_RIGHT;\r
+    }\r
+\r
+    /**\r
+     * The key that selects the previous day in the calendar. By default this is\r
+     * the left arrow key but by overriding this method it can be changed to\r
+     * whatever you like.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getBackwardKey() {\r
+        return KeyCodes.KEY_LEFT;\r
+    }\r
+\r
+    /**\r
+     * The key that selects the next week in the calendar. By default this is\r
+     * the down arrow key but by overriding this method it can be changed to\r
+     * whatever you like.\r
+     * \r
+     * @return\r
+     */\r
+    protected int getNextKey() {\r
+        return KeyCodes.KEY_DOWN;\r
     }\r
 \r
     /**\r
-     * Sets focus to Calendar panel.\r
+     * The key that selects the previous week in the calendar. By default this\r
+     * is the up arrow key but by overriding this method it can be changed to\r
+     * whatever you like.\r
      * \r
-     * @param focus\r
+     * @return\r
      */\r
-    public void setFocus(boolean focus) {\r
-        nextYear.setFocus(focus);\r
+    protected int getPreviousKey() {\r
+        return KeyCodes.KEY_UP;\r
     }\r
 \r
 }\r
index b8a004e6996e075a52678b7a2737b4ebe5447644..7c87927fd3a3ec0d96d86eeae33096b2911d927b 100644 (file)
@@ -6,9 +6,11 @@ package com.vaadin.terminal.gwt.client.ui;
 \r
 import com.google.gwt.event.dom.client.ClickEvent;\r
 import com.google.gwt.event.dom.client.ClickHandler;\r
+import com.google.gwt.event.dom.client.KeyCodes;\r
 import com.google.gwt.event.logical.shared.CloseEvent;\r
 import com.google.gwt.event.logical.shared.CloseHandler;\r
 import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Event;\r
 import com.google.gwt.user.client.Timer;\r
 import com.google.gwt.user.client.Window;\r
 import com.google.gwt.user.client.ui.Button;\r
@@ -18,12 +20,22 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.client.Paintable;\r
 import com.vaadin.terminal.gwt.client.UIDL;\r
 \r
+/**\r
+ * Represents a date selection component with a text field and a popup date\r
+ * selector.\r
+ * \r
+ * <b>Note:</b> To change the keyboard assignments used in the popup dialog you\r
+ * should extend <code>com.vaadin.terminal.gwt.client.ui.VCalendarPanel</code>\r
+ * and then pass set it by calling the\r
+ * <code>setCalendarPanel(VCalendarPanel panel)</code> method.\r
+ * \r
+ */\r
 public class VPopupCalendar extends VTextualDate implements Paintable, Field,\r
         ClickHandler, CloseHandler<PopupPanel> {\r
 \r
     private final Button calendarToggle;\r
 \r
-    private final VCalendarPanel calendar;\r
+    private VCalendarPanel calendar;\r
 \r
     private final VOverlay popup;\r
     private boolean open = false;\r
@@ -36,19 +48,31 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
         calendarToggle.setStyleName(CLASSNAME + "-button");\r
         calendarToggle.setText("");\r
         calendarToggle.addClickHandler(this);\r
+        calendarToggle.getElement().setTabIndex(-1);\r
         add(calendarToggle);\r
 \r
         calendar = new VCalendarPanel(this);\r
+\r
         popup = new VOverlay(true, true, true);\r
         popup.setStyleName(VDateField.CLASSNAME + "-popup");\r
         popup.setWidget(calendar);\r
-        popup.addCloseHandler(this);\r
+        popup.addCloseHandler(this);        \r
 \r
         DOM.setElementProperty(calendar.getElement(), "id",\r
                 "PID_VAADIN_POPUPCAL");\r
 \r
+        sinkEvents(Event.ONKEYDOWN);\r
+\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin\r
+     * .terminal.gwt.client.UIDL,\r
+     * com.vaadin.terminal.gwt.client.ApplicationConnection)\r
+     */\r
     @Override\r
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
         boolean lastReadOnlyState = readonly;\r
@@ -64,13 +88,6 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
         }\r
         calendarToggle.setEnabled(enabled);\r
 \r
-        // not a FocusWidget -> needs own tabindex handling\r
-        if (uidl.hasAttribute("tabindex")) {\r
-            // Set the same tab index as for the textfield. Tabbing then works\r
-            // as expected.\r
-            calendarToggle.setTabIndex(uidl.getIntAttribute("tabindex"));\r
-        }\r
-\r
         if (readonly) {\r
             calendarToggle.addStyleName(CLASSNAME + "-button-readonly");\r
         } else {\r
@@ -84,16 +101,42 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
         calendarToggle.setEnabled(true);\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)\r
+     */\r
     @Override\r
     public void setStyleName(String style) {\r
         // make sure the style is there before size calculation\r
         super.setStyleName(style + " " + CLASSNAME + "-popupcalendar");\r
     }\r
 \r
-    public void onClick(ClickEvent event) {\r
-        if (event.getSource() == calendarToggle && !open && !readonly) {\r
+    /**\r
+     * Set the popup panel to be displayed when clicking the calendar button.\r
+     * This is usually used when we want to extend the VCalendarPanel and use\r
+     * new keyboard bindings.\r
+     * \r
+     * @param panel\r
+     *            The custom calendar panel\r
+     */\r
+    public void setCalendarPanel(VCalendarPanel panel) {\r
+        if (panel != null) {\r
+            calendar = panel;\r
+            calendar.updateCalendar();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Opens the calendar panel popup\r
+     */\r
+    public void openCalendarPanel() {\r
+\r
+        if (!open && !readonly) {\r
             open = true;\r
             calendar.updateCalendar();\r
+\r
             // clear previous values\r
             popup.setWidth("");\r
             popup.setHeight("");\r
@@ -140,12 +183,43 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
                     popup.setPopupPosition(l, t\r
                             + calendarToggle.getOffsetHeight() + 2);\r
 \r
-                    setFocus(true);\r
+                    /* We have to wait a while before focusing\r
+                     * since the popup needs to be opened before \r
+                     * we can focus\r
+                     */\r
+                    Timer focusTimer = new Timer() {                        \r
+                        @Override\r
+                        public void run() {\r
+                            setFocus(true);                            \r
+                        }\r
+                    };\r
+                    \r
+                    focusTimer.schedule(100);\r
                 }\r
             });\r
         }\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event\r
+     * .dom.client.ClickEvent)\r
+     */\r
+    public void onClick(ClickEvent event) {\r
+        if (event.getSource() == calendarToggle) {\r
+            openCalendarPanel();\r
+        }\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt\r
+     * .event.logical.shared.CloseEvent)\r
+     */\r
     public void onClose(CloseEvent<PopupPanel> event) {\r
         if (event.getSource() == popup) {\r
             buildDate();\r
@@ -169,6 +243,11 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
         calendar.setFocus(focus);\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#getFieldExtraWidth()\r
+     */\r
     @Override\r
     protected int getFieldExtraWidth() {\r
         if (fieldExtraWidth < 0) {\r
@@ -178,6 +257,11 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
         return fieldExtraWidth;\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#buildDate()\r
+     */\r
     @Override\r
     protected void buildDate() {\r
         // Save previous value\r
@@ -189,4 +273,41 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
             setText(previousValue);\r
         }\r
     }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.vaadin.terminal.gwt.client.ui.VDateField#onBrowserEvent(com.google\r
+     * .gwt.user.client.Event)\r
+     */\r
+    @Override\r
+    public void onBrowserEvent(com.google.gwt.user.client.Event event) {\r
+        super.onBrowserEvent(event);\r
+        if (DOM.eventGetType(event) == Event.ONKEYDOWN\r
+                && event.getKeyCode() == getOpenCalenderPanelKey()) {\r
+            openCalendarPanel();\r
+            event.preventDefault();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Get the key code that opens the calendar panel. By default it is the down\r
+     * key but you can override this to be whatever you like\r
+     * \r
+     * @return\r
+     */\r
+    protected int getOpenCalenderPanelKey() {\r
+        return KeyCodes.KEY_DOWN;\r
+    }\r
+\r
+    /**\r
+     * Closes the open popup panel\r
+     */\r
+    public void closeCalendarPanel() {\r
+        if (open) {\r
+            popup.hide(true);\r
+        }\r
+    }\r
+\r
 }\r
index 6f600376554adcfc19aa8b28eab9d2d93d33ba42..99cf94b4c45977f976e6975e49ab514f45b59eb6 100644 (file)
@@ -8,13 +8,25 @@ import java.util.Date;
 \r
 import com.google.gwt.event.dom.client.ChangeEvent;\r
 import com.google.gwt.event.dom.client.ChangeHandler;\r
-import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.event.dom.client.FocusEvent;\r
+import com.google.gwt.event.dom.client.FocusHandler;\r
+import com.google.gwt.event.dom.client.KeyCodes;\r
+import com.google.gwt.event.dom.client.KeyDownEvent;\r
+import com.google.gwt.event.dom.client.KeyDownHandler;\r
+import com.google.gwt.event.dom.client.KeyPressEvent;\r
+import com.google.gwt.event.dom.client.KeyPressHandler;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.Timer;\r
 import com.google.gwt.user.client.ui.ListBox;\r
+import com.vaadin.terminal.gwt.client.BrowserInfo;\r
 \r
-public class VTime extends FlowPanel implements ChangeHandler {\r
+public class VTime extends FocusableFlowPanel implements ChangeHandler,\r
+        KeyPressHandler, KeyDownHandler, FocusHandler {\r
 \r
     private final VDateField datefield;\r
 \r
+    private final VCalendarPanel calendar;\r
+\r
     private ListBox hours;\r
 \r
     private ListBox mins;\r
@@ -23,18 +35,85 @@ public class VTime extends FlowPanel implements ChangeHandler {
 \r
     private ListBox msec;\r
 \r
-    private ListBox ampm;\r
+    private AMPMListBox ampm;\r
 \r
     private int resolution = VDateField.RESOLUTION_HOUR;\r
 \r
     private boolean readonly;\r
 \r
-    public VTime(VDateField parent) {\r
+    /**\r
+     * The AM/PM Listbox yields the keyboard focus to the calendar\r
+     */\r
+    private class AMPMListBox extends ListBox {\r
+        public AMPMListBox() {\r
+            super();\r
+            sinkEvents(Event.ONKEYDOWN);\r
+        }\r
+\r
+        /*\r
+         * (non-Javadoc)\r
+         * \r
+         * @see\r
+         * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt\r
+         * .user.client.Event)\r
+         */\r
+        @Override\r
+        public void onBrowserEvent(Event event) {\r
+            super.onBrowserEvent(event);\r
+\r
+            if (event.getKeyCode() == KeyCodes.KEY_TAB) {\r
+                event.preventDefault();\r
+\r
+                /*\r
+                 * Wait until the current event has been processed. If the timer\r
+                 * is left out the focus will move to the VTime-class not the\r
+                 * panel itself. Weird.\r
+                 */\r
+                Timer t = new Timer() {\r
+                    @Override\r
+                    public void run() {\r
+                        calendar.setFocus(true);\r
+                    }\r
+                };\r
+                t.schedule(1);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Constructor\r
+     * \r
+     * @param parent\r
+     *            The DateField related to this instance\r
+     * @param panel\r
+     *            The panel where this this instance is embedded\r
+     */\r
+    public VTime(VDateField parent, VCalendarPanel panel) {\r
         super();\r
         datefield = parent;\r
+        calendar = panel;\r
         setStyleName(VDateField.CLASSNAME + "-time");\r
+\r
+        /*\r
+         * Firefox auto-repeat works correctly only if we use a key press\r
+         * handler, other browsers handle it correctly when using a key down\r
+         * handler\r
+         */\r
+        if (BrowserInfo.get().isGecko()) {\r
+            addKeyPressHandler(this);\r
+        } else {\r
+            addKeyDownHandler(this);\r
+        }\r
+\r
+        addFocusHandler(this);\r
     }\r
 \r
+    /**\r
+     * Constructs the ListBoxes and updates their value\r
+     * \r
+     * @param redraw\r
+     *            Should new instances of the listboxes be created\r
+     */\r
     private void buildTime(boolean redraw) {\r
         final boolean thc = datefield.getDateTimeService().isTwelveHourClock();\r
         if (redraw) {\r
@@ -47,10 +126,11 @@ public class VTime extends FlowPanel implements ChangeHandler {
             }\r
             hours.addChangeHandler(this);\r
             if (thc) {\r
-                ampm = new ListBox();\r
+                ampm = new AMPMListBox();\r
                 ampm.setStyleName(VNativeSelect.CLASSNAME);\r
                 final String[] ampmText = datefield.getDateTimeService()\r
                         .getAmPmStrings();\r
+                calendar.setFocus(true);\r
                 ampm.addItem(ampmText[0]);\r
                 ampm.addItem(ampmText[1]);\r
                 ampm.addChangeHandler(this);\r
@@ -151,7 +231,7 @@ public class VTime extends FlowPanel implements ChangeHandler {
         }\r
 \r
         // Update times\r
-        Date cdate = datefield.getCurrentDate();\r
+        Date cdate = datefield.getShowingDate();\r
         boolean selected = true;\r
         if (cdate == null) {\r
             cdate = new Date();\r
@@ -236,6 +316,12 @@ public class VTime extends FlowPanel implements ChangeHandler {
 \r
     }\r
 \r
+    /**\r
+     * Update the time ListBoxes\r
+     * \r
+     * @param redraw\r
+     *            Should new instances of the listboxes be created\r
+     */\r
     public void updateTime(boolean redraw) {\r
         buildTime(redraw || resolution != datefield.getCurrentResolution()\r
                 || readonly != datefield.isReadonly());\r
@@ -246,6 +332,13 @@ public class VTime extends FlowPanel implements ChangeHandler {
         readonly = datefield.isReadonly();\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt\r
+     * .event.dom.client.ChangeEvent)\r
+     */\r
     public void onChange(ChangeEvent event) {\r
         if (datefield.getCurrentDate() == null) {\r
             // was null on server, need to set\r
@@ -257,61 +350,115 @@ public class VTime extends FlowPanel implements ChangeHandler {
             datefield.setCurrentDate(new Date(now.getTime()));\r
 \r
             // Init variables with current time\r
-            datefield.getClient().updateVariable(datefield.getId(), "year",\r
-                    now.getYear() + 1900, false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "month",\r
-                    now.getMonth() + 1, false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "day",\r
-                    now.getDate(), false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "hour",\r
-                    now.getHours(), false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "min",\r
-                    now.getMinutes(), false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "sec",\r
-                    now.getSeconds(), false);\r
-            datefield.getClient().updateVariable(datefield.getId(), "msec",\r
-                    datefield.getMilliseconds(), false);\r
+            notifyServerOfChanges();\r
         }\r
         if (event.getSource() == hours) {\r
             int h = hours.getSelectedIndex();\r
             if (datefield.getDateTimeService().isTwelveHourClock()) {\r
                 h = h + ampm.getSelectedIndex() * 12;\r
             }\r
-            datefield.getCurrentDate().setHours(h);\r
             datefield.getShowingDate().setHours(h);\r
-            datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
-                    datefield.isImmediate());\r
             updateTime(false);\r
         } else if (event.getSource() == mins) {\r
             final int m = mins.getSelectedIndex();\r
-            datefield.getCurrentDate().setMinutes(m);\r
             datefield.getShowingDate().setMinutes(m);\r
-            datefield.getClient().updateVariable(datefield.getId(), "min", m,\r
-                    datefield.isImmediate());\r
             updateTime(false);\r
         } else if (event.getSource() == sec) {\r
             final int s = sec.getSelectedIndex();\r
-            datefield.getCurrentDate().setSeconds(s);\r
             datefield.getShowingDate().setSeconds(s);\r
-            datefield.getClient().updateVariable(datefield.getId(), "sec", s,\r
-                    datefield.isImmediate());\r
             updateTime(false);\r
         } else if (event.getSource() == msec) {\r
             final int ms = msec.getSelectedIndex();\r
-            datefield.setMilliseconds(ms);\r
             datefield.setShowingMilliseconds(ms);\r
-            datefield.getClient().updateVariable(datefield.getId(), "msec", ms,\r
-                    datefield.isImmediate());\r
             updateTime(false);\r
         } else if (event.getSource() == ampm) {\r
-            final int h = hours.getSelectedIndex() + ampm.getSelectedIndex()\r
-                    * 12;\r
-            datefield.getCurrentDate().setHours(h);\r
+            final int h = hours.getSelectedIndex()\r
+                    + (ampm.getSelectedIndex() * 12);\r
             datefield.getShowingDate().setHours(h);\r
-            datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
-                    datefield.isImmediate());\r
             updateTime(false);\r
         }\r
     }\r
 \r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google\r
+     * .gwt.event.dom.client.KeyPressEvent)\r
+     */\r
+    public void onKeyPress(KeyPressEvent event) {\r
+        int keycode = event.getNativeEvent().getKeyCode();\r
+\r
+        if (calendar != null) {\r
+            if (keycode == calendar.getSelectKey()\r
+                    || keycode == calendar.getCloseKey()) {\r
+                if (keycode == calendar.getSelectKey()) {\r
+                    notifyServerOfChanges();\r
+                }\r
+\r
+                calendar.handleNavigation(keycode, event.getNativeEvent()\r
+                        .getCtrlKey()\r
+                        || event.getNativeEvent().getMetaKey(), event\r
+                        .getNativeEvent().getShiftKey());\r
+                return;\r
+            }\r
+        }\r
+\r
+        event.stopPropagation();\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt\r
+     * .event.dom.client.KeyDownEvent)\r
+     */\r
+    public void onKeyDown(KeyDownEvent event) {\r
+        int keycode = event.getNativeEvent().getKeyCode();\r
+        if (keycode != calendar.getCloseKey()\r
+                && keycode != calendar.getSelectKey()) {\r
+            event.stopPropagation();\r
+        }\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see\r
+     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event\r
+     * .dom.client.FocusEvent)\r
+     */\r
+    public void onFocus(FocusEvent event) {\r
+        event.preventDefault();\r
+\r
+        // Delegate focus to the hour select\r
+        hours.setFocus(true);\r
+    }\r
+\r
+    /**\r
+     * Update the variables server side\r
+     */\r
+    public void notifyServerOfChanges() {\r
+        /*\r
+         * Just update the variables, don't send any thing. The calendar panel\r
+         * will make the request when the panel is closed.\r
+         */\r
+        Date now = datefield.getCurrentDate();\r
+        datefield.getClient().updateVariable(datefield.getId(), "year",\r
+                now.getYear() + 1900, false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "month",\r
+                now.getMonth() + 1, false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "day",\r
+                now.getDate(), false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "hour",\r
+                now.getHours(), false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "min",\r
+                now.getMinutes(), false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "sec",\r
+                now.getSeconds(), false);\r
+        datefield.getClient().updateVariable(datefield.getId(), "msec",\r
+                datefield.getShowingMilliseconds(), false);\r
+    }\r
+\r
 }\r