]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge branch '6.8'
authorJohn Ahlroos <john@vaadin.com>
Mon, 10 Sep 2012 09:32:24 +0000 (12:32 +0300)
committerJohn Ahlroos <john@vaadin.com>
Mon, 10 Sep 2012 09:32:24 +0000 (12:32 +0300)
Conflicts:
WebContent/VAADIN/themes/base/common/common.scss
server/src/com/vaadin/server/VaadinServlet.java
server/src/com/vaadin/ui/AbstractField.java
src/com/vaadin/terminal/gwt/client/ui/VUriFragmentUtility.java
tests/testbench/com/vaadin/tests/components/datefield/DateFieldExtendedRange.java
tests/testbench/com/vaadin/tests/components/datefield/PopupDateFieldExtendedRange.java
tests/testbench/com/vaadin/tests/components/table/TableReduceContainerSize.java
tests/testbench/com/vaadin/tests/tickets/Ticket8291.java
uitest/src/com/vaadin/tests/components/datefield/PopupDateFieldExtendedRange.html
uitest/src/com/vaadin/tests/components/datefield/PopupDateFieldExtendedRange.java
uitest/src/com/vaadin/tests/tickets/Ticket8291.java
uitest/test.xml

1  2 
WebContent/VAADIN/themes/base/common/common.scss
client-compiler/src/com/vaadin/tools/WidgetsetCompiler.java
client/src/com/vaadin/client/BrowserInfo.java
client/src/com/vaadin/client/ui/datefield/VCalendarPanel.java
server/src/com/vaadin/ui/AbstractField.java
uitest/src/com/vaadin/tests/components/datefield/DateFieldExtendedRange.html
uitest/src/com/vaadin/tests/components/datefield/DateFieldExtendedRange.java
uitest/src/com/vaadin/tests/components/table/TableReduceContainerSize.html
uitest/src/com/vaadin/tests/components/table/TableReduceContainerSize.java

index 40bea2c9d64242a1d5394a67db9dee12a6753bfc,0000000000000000000000000000000000000000..7cd1f2dd1c2c9c228249a8f632ca8890df3b120a
mode 100644,000000..100644
--- /dev/null
@@@ -1,255 -1,0 +1,258 @@@
 +@mixin base-common {
 +
 +/* References the BODY tag generated by Vaadin application servlet */
 +.v-generated-body {
 +      width: 100%;
 +      height: 100%;
 +      border: 0;
 +      margin: 0;
 +      overflow: hidden;
 +}
 +.v-app {
 +      height: 100%;
 +}
 +/* Force arrow cursor for all elements inside the app */
 +.v-app,
 +.v-window,
 +.v-popupview-popup,
 +.v-label,
 +.v-caption {
 +      cursor: default;
 +}
 +div.v-app-loading {
 +      /* You can use this to provide indication for the user that the application is loading. */
 +      /* It is applied to the same element as .v-app */
 +      background-image: url(img/loading-indicator.gif);
 +      background-repeat: no-repeat;
 +      background-position: 50%;
 +      width: 100%;
 +      height: 100%;
 +}
 +.v-view {
 +      height: 100%;
 +      width: 100%;
 +      /* avoid scrollbars with margins in root layout */
 +      outline: none;
 +      position: relative;
 +}
 +/* Prevent margin collapse */
 +.v-view.v-view-embedded {
 +      margin-top: -1px;
 +      border-top: 1px solid transparent;
 +}
 +/**
 + * Try to handle printing somehow. Reasonable printing support 
 + * needs application specific planning and CSS tuning.
 + */
 +@media print {
 +      .v-generated-body {
 +              height: auto;
 +              min-height: 20cm;
 +              overflow: visible;
 +      }
 +      .v-app {
 +              height: auto;
 +              min-height: 20cm;
 +      }
 +      .v-view {
 +              overflow: visible;
 +      }
 +      .v-gridlayout {
 +              overflow: visible !important;
 +      }
 +}
 +.v-view:active,
 +.v-view:focus {
 +      outline: none;
 +}
 +.v-app select,
 +.v-window select {
 +      margin: 0;
 +}
 +.v-disabled {
 +      opacity: .3;
 +      filter: alpha(opacity=30);
 +      cursor: default;
 +}
 +.v-disabled * {
 +      cursor: default;
 +}
 +* html .v-disabled {
 +      zoom: 1;
 +}
 +*+html .v-disabled {
 +      zoom: 1;
 +}
 +.v-disabled .v-disabled {
 +      opacity: 1;
 +}
 +.v-required-field-indicator {
 +      padding-left: 2px;
 +      color: red;
 +}
 +.v-form fieldset {
 +      border: none;
 +      padding: 0;
 +      margin: 0;
 +      height: 100%;
 +}
 +.v-form-content {
 +      height: 100%;
 +      -webkit-box-sizing: border-box;
 +      -moz-box-sizing: border-box;
 +      box-sizing: border-box;
 +}
 +
 +/* Field modified */ /* Disabled by default
 +.v-modified,
 +.v-richtextarea.v-modified iframe.gwt-RichTextArea,
 +.v-checkbox.v-modified,
 +.v-modified .v-select-option,
 +.v-modified .v-textfield,
 +.v-modified .v-datefield-calendarpanel,
 +.v-modified .v-select-select,
 +.v-modified .v-select-twincol-selections,
 +.v-modified .v-select-twincol-options,
 +.v-modified .v-slider-base {
 +      background: #ffffe0;
 +}
 +*/
 +.v-tooltip {
 +      cursor: default;
 +      background: #fff;
 +}
 +.v-tooltip-text {
 +      overflow: auto;
 +}
 +.v-tooltip .v-errormessage {
 +      overflow: auto;
 +}
 +.v-contextmenu {
 +      background: #fff;
 +}
 +.v-contextmenu .gwt-MenuItem {
 +      cursor: pointer;
 +      vertical-align: middle;
 +      padding: 0;
 +      border: 0;
 +      margin: 0;
 +}
 +.v-contextmenu .gwt-MenuItem div {
 +      cursor: pointer;
 +      vertical-align: middle;
 +      white-space: nowrap;
 +}
 +.v-contextmenu .gwt-MenuItem-selected div {
 +      background: #aaa;
 +      color: #fff;
 +}
 +.v-contextmenu table {
 +      border-collapse: collapse;
 +      margin: 0;
 +      padding: 0;
 +}
 +.v-contextmenu .gwt-MenuItem img {
 +      margin-right: 1em;
 +      vertical-align: middle;
 +}
 +/* Margins are not supported within Label */
 +.v-label pre {
 +      margin: 0;
 +}
 +/* A label with undefined width is always considered to be on one line */
 +.v-label-undef-w {
 +      white-space: nowrap;
 +}
 +/* Revert line-height for heading elements inside labels */
 +.v-label h1,
 +.v-label h2,
 +.v-label h3,
 +.v-label h4,
 +.v-label h5,
 +.v-label h6 {
 +      line-height: normal;
 +} 
 +/* Loading indicator states
 + * Note: client side expects that loading indicator has a height. It depends on
 + * this css property to ensure browsers have applied all required styles.
 + */
 +.v-loading-indicator,
 +.v-loading-indicator-delay,
 +.v-loading-indicator-wait {
 +      position: absolute;
 +      top: 0;
 +      right: 0;
 +      z-index: 30000;
 +      width: 31px;
 +      height: 31px;
 +      background: transparent url(img/loading-indicator.gif);
 +      margin-right: 5px;
 +      margin-top: 5px;
 +}
 +.v-loading-indicator-delay {
 +      background-image: url(img/loading-indicator-delay.gif);
 +}
 +.v-loading-indicator-wait {
 +      background-image: url(img/loading-indicator-wait.gif);
 +}
 +/* Debug dialog */
 +.v-debug-console {
 +      background: #fff;
 +      opacity: .9;
 +      border: 1px solid #000;
 +      font-family: sans-serif;
 +}
 +.v-debug-console-caption {
 +      background: #000;
 +      border-bottom: 1px solid grey;
 +      color: white;
 +      font-weight: bold;
 +}
 +.v-debug-console-content {
 +      font-size: x-small;
 +      overflow: auto;
 +      white-space: pre;
 +}
 +.v-debug-console-content input {
 +      font-size: xx-small;
 +}
 +/* Debug style */
 +.v-app .invalidlayout,
 +.v-app .invalidlayout * {
 +      background: #f99 !important;
 +}
 +/* Fix for Liferay, issue #2384 */
 +.v-app input[type="text"],
 +.v-app input[type="password"],
 +.v-app input[type="reset"],
 +.v-app select,
 +.v-app textarea ,
 +.v-window input[type="text"],
 +.v-window input[type="password"],
 +.v-window input[type="reset"],
 +.v-window select,
 +.v-window textarea {
 +      padding: 2px;
 +}
 +
 +.v-drag-element {
 +      z-index: 60000;
 +      /* override any other position: properties */
 +      position: absolute !important;
 +      opacity: 0.5;
 +      filter: alpha(opacity=50);
 +      cursor: default;
 +}
 +
 +.v-clip {
 +      overflow: hidden;
 +}
 +
 +.v-scrollable {
 +      overflow: auto;
 +}
 +
++/* Enable kinetic scrolling on Mobile Safari 6 */ 
++.v-ios.v-sa6 .v-scrollable { 
++        -webkit-overflow-scrolling: touch; 
 +}
index 51d5ec8187a7774f925f28e0d4c8fb8a8767e689,0000000000000000000000000000000000000000..854efe54d6345f7c63cff59ee85134ecd7e40065
mode 100755,000000..100755
--- /dev/null
@@@ -1,106 -1,0 +1,108 @@@
 +/*
 + * Copyright 2011 Vaadin Ltd.
 + * 
 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 + * use this file except in compliance with the License. You may obtain a copy of
 + * the License at
 + * 
 + * http://www.apache.org/licenses/LICENSE-2.0
 + * 
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 + * License for the specific language governing permissions and limitations under
 + * the License.
 + */
 +package com.vaadin.tools;
 +
 +import java.lang.reflect.Method;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.server.widgetsetutils.WidgetSetBuilder;
 +
 +/**
 + * A wrapper for the GWT 1.6 compiler that runs the compiler in a new thread.
 + * 
 + * This allows circumventing a J2SE 5.0 bug (6316197) that prevents setting the
 + * stack size for the main thread. Thus, larger widgetsets can be compiled.
 + * 
 + * This class takes the same command line arguments as the
 + * com.google.gwt.dev.GWTCompiler class. The old and deprecated compiler is used
 + * for compatibility with GWT 1.5.
 + * 
 + * A typical invocation would use e.g. the following arguments
 + * 
 + * "-out WebContent/VAADIN/widgetsets com.vaadin.DefaultWidgetSet"
 + * 
 + * In addition, larger memory usage settings for the VM should be used, e.g.
 + * 
 + * "-Xms256M -Xmx512M -Xss8M"
 + * 
 + * The source directory containing widgetset and related classes must be
 + * included in the classpath, as well as the gwt-dev-[platform].jar and other
 + * relevant JARs.
 + * 
 + * @deprecated with Java 6, can use com.google.gwt.dev.Compiler directly (also
 + *             in Eclipse plug-in etc.)
 + */
 +@Deprecated
 +public class WidgetsetCompiler {
 +
 +    /**
 +     * @param args
 +     *            same arguments as for com.google.gwt.dev.Compiler
 +     */
 +    public static void main(final String[] args) {
 +        try {
 +            // run the compiler in a different thread to enable using the
 +            // user-set stack size
 +
 +            // on Windows, the default stack size is too small for the main
 +            // thread and cannot be changed in JRE 1.5 (see
 +            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6316197)
 +
 +            Runnable runCompiler = new Runnable() {
 +                @Override
 +                public void run() {
 +                    try {
 +                        // GWTCompiler.main(args);
 +                        // avoid warnings
 +
 +                        String wsname = args[args.length - 1];
 +
 +                        // TODO expecting this is launched via eclipse WTP
 +                        // project
 +                        System.out
 +                                .println("Updating GWT module description file...");
 +                        WidgetSetBuilder.updateWidgetSet(wsname);
 +                        System.out.println("Done.");
 +
 +                        System.out.println("Starting GWT compiler");
 +                        System.setProperty("gwt.nowarn.legacy.tools", "true");
++                        System.setProperty("gwt.forceVersionCheckURL",
++                                "http://tools.vaadin.com/version/currentversion.xml");
 +                        Class<?> compilerClass = Class
 +                                .forName("com.google.gwt.dev.GWTCompiler");
 +                        Method method = compilerClass.getDeclaredMethod("main",
 +                                String[].class);
 +                        method.invoke(null, new Object[] { args });
 +                    } catch (Throwable thr) {
 +                        getLogger().log(Level.SEVERE,
 +                                "Widgetset compilation failed", thr);
 +                    }
 +                }
 +            };
 +            Thread runThread = new Thread(runCompiler);
 +            runThread.start();
 +            runThread.join();
 +            System.out.println("Widgetset compilation finished");
 +        } catch (Throwable thr) {
 +            getLogger().log(Level.SEVERE, "Widgetset compilation failed", thr);
 +        }
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(WidgetsetCompiler.class.getName());
 +    }
 +}
index f7c8d2f33181ed63802f4d647c3b15b7526fba27,0000000000000000000000000000000000000000..e32e9b65f0f20dba8f23ed66cd9d56e1134ab307
mode 100644,000000..100644
--- /dev/null
@@@ -1,435 -1,0 +1,437 @@@
-         // Cannot enable native touch scrolling on iOS 5 until #8792 is resolved
-         // if (isIOS() && isWebkit() && getWebkitVersion() >= 534) {
-         // return false;
-         // }
 +/* 
 + * Copyright 2011 Vaadin Ltd.
 + * 
 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 + * use this file except in compliance with the License. You may obtain a copy of
 + * the License at
 + * 
 + * http://www.apache.org/licenses/LICENSE-2.0
 + * 
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 + * License for the specific language governing permissions and limitations under
 + * the License.
 + */
 +
 +package com.vaadin.client;
 +
 +import com.google.gwt.user.client.ui.RootPanel;
 +import com.vaadin.shared.VBrowserDetails;
 +
 +/**
 + * Class used to query information about web browser.
 + * 
 + * Browser details are detected only once and those are stored in this singleton
 + * class.
 + * 
 + */
 +public class BrowserInfo {
 +
 +    private static final String BROWSER_OPERA = "op";
 +    private static final String BROWSER_IE = "ie";
 +    private static final String BROWSER_FIREFOX = "ff";
 +    private static final String BROWSER_SAFARI = "sa";
 +
 +    public static final String ENGINE_GECKO = "gecko";
 +    public static final String ENGINE_WEBKIT = "webkit";
 +    public static final String ENGINE_PRESTO = "presto";
 +    public static final String ENGINE_TRIDENT = "trident";
 +
 +    private static final String OS_WINDOWS = "win";
 +    private static final String OS_LINUX = "lin";
 +    private static final String OS_MACOSX = "mac";
 +    private static final String OS_ANDROID = "android";
 +    private static final String OS_IOS = "ios";
 +
 +    // Common CSS class for all touch devices
 +    private static final String UI_TOUCH = "touch";
 +
 +    private static BrowserInfo instance;
 +
 +    private static String cssClass = null;
 +
 +    static {
 +        // Add browser dependent v-* classnames to body to help css hacks
 +        String browserClassnames = get().getCSSClass();
 +        RootPanel.get().addStyleName(browserClassnames);
 +    }
 +
 +    /**
 +     * Singleton method to get BrowserInfo object.
 +     * 
 +     * @return instance of BrowserInfo object
 +     */
 +    public static BrowserInfo get() {
 +        if (instance == null) {
 +            instance = new BrowserInfo();
 +        }
 +        return instance;
 +    }
 +
 +    private VBrowserDetails browserDetails;
 +    private boolean touchDevice;
 +
 +    private BrowserInfo() {
 +        browserDetails = new VBrowserDetails(getBrowserString());
 +        if (browserDetails.isIE()) {
 +            // Use document mode instead user agent to accurately detect how we
 +            // are rendering
 +            int documentMode = getIEDocumentMode();
 +            if (documentMode != -1) {
 +                browserDetails.setIEMode(documentMode);
 +            }
 +        }
 +
 +        if (browserDetails.isChrome()) {
 +            touchDevice = detectChromeTouchDevice();
 +        } else {
 +            touchDevice = detectTouchDevice();
 +        }
 +    }
 +
 +    private native boolean detectTouchDevice()
 +    /*-{
 +        try { document.createEvent("TouchEvent");return true;} catch(e){return false;};
 +    }-*/;
 +
 +    private native boolean detectChromeTouchDevice()
 +    /*-{
 +        return ("ontouchstart" in window);
 +    }-*/;
 +
 +    private native int getIEDocumentMode()
 +    /*-{
 +      var mode = $wnd.document.documentMode;
 +      if (!mode)
 +               return -1;
 +      return mode;
 +    }-*/;
 +
 +    /**
 +     * Returns a string representing the browser in use, for use in CSS
 +     * classnames. The classnames will be space separated abbreviations,
 +     * optionally with a version appended.
 +     * 
 +     * Abbreviations: Firefox: ff Internet Explorer: ie Safari: sa Opera: op
 +     * 
 +     * Browsers that CSS-wise behave like each other will get the same
 +     * abbreviation (this usually depends on the rendering engine).
 +     * 
 +     * This is quite simple at the moment, more heuristics will be added when
 +     * needed.
 +     * 
 +     * Examples: Internet Explorer 6: ".v-ie .v-ie6 .v-ie60", Firefox 3.0.4:
 +     * ".v-ff .v-ff3 .v-ff30", Opera 9.60: ".v-op .v-op9 .v-op960", Opera 10.10:
 +     * ".v-op .v-op10 .v-op1010"
 +     * 
 +     * @return
 +     */
 +    public String getCSSClass() {
 +        String prefix = "v-";
 +
 +        if (cssClass == null) {
 +            String browserIdentifier = "";
 +            String majorVersionClass = "";
 +            String minorVersionClass = "";
 +            String browserEngineClass = "";
 +
 +            if (browserDetails.isFirefox()) {
 +                browserIdentifier = BROWSER_FIREFOX;
 +                majorVersionClass = browserIdentifier
 +                        + getBrowserMajorVersion();
 +                minorVersionClass = majorVersionClass
 +                        + browserDetails.getBrowserMinorVersion();
 +                browserEngineClass = ENGINE_GECKO;
 +            } else if (browserDetails.isChrome()) {
 +                // TODO update when Chrome is more stable
 +                browserIdentifier = BROWSER_SAFARI;
 +                majorVersionClass = "ch";
 +                browserEngineClass = ENGINE_WEBKIT;
 +            } else if (browserDetails.isSafari()) {
 +                browserIdentifier = BROWSER_SAFARI;
 +                majorVersionClass = browserIdentifier
 +                        + getBrowserMajorVersion();
 +                minorVersionClass = majorVersionClass
 +                        + browserDetails.getBrowserMinorVersion();
 +                browserEngineClass = ENGINE_WEBKIT;
 +            } else if (browserDetails.isIE()) {
 +                browserIdentifier = BROWSER_IE;
 +                majorVersionClass = browserIdentifier
 +                        + getBrowserMajorVersion();
 +                minorVersionClass = majorVersionClass
 +                        + browserDetails.getBrowserMinorVersion();
 +                browserEngineClass = ENGINE_TRIDENT;
 +            } else if (browserDetails.isOpera()) {
 +                browserIdentifier = BROWSER_OPERA;
 +                majorVersionClass = browserIdentifier
 +                        + getBrowserMajorVersion();
 +                minorVersionClass = majorVersionClass
 +                        + browserDetails.getBrowserMinorVersion();
 +                browserEngineClass = ENGINE_PRESTO;
 +            }
 +
 +            cssClass = prefix + browserIdentifier;
 +            if (!"".equals(majorVersionClass)) {
 +                cssClass = cssClass + " " + prefix + majorVersionClass;
 +            }
 +            if (!"".equals(minorVersionClass)) {
 +                cssClass = cssClass + " " + prefix + minorVersionClass;
 +            }
 +            if (!"".equals(browserEngineClass)) {
 +                cssClass = cssClass + " " + prefix + browserEngineClass;
 +            }
 +            String osClass = getOperatingSystemClass();
 +            if (osClass != null) {
 +                cssClass = cssClass + " " + prefix + osClass;
 +            }
 +            if (isTouchDevice()) {
 +                cssClass = cssClass + " " + prefix + UI_TOUCH;
 +            }
 +        }
 +
 +        return cssClass;
 +    }
 +
 +    private String getOperatingSystemClass() {
 +        if (browserDetails.isAndroid()) {
 +            return OS_ANDROID;
 +        } else if (browserDetails.isIOS()) {
 +            return OS_IOS;
 +        } else if (browserDetails.isWindows()) {
 +            return OS_WINDOWS;
 +        } else if (browserDetails.isLinux()) {
 +            return OS_LINUX;
 +        } else if (browserDetails.isMacOSX()) {
 +            return OS_MACOSX;
 +        }
 +        // Unknown OS
 +        return null;
 +    }
 +
 +    public boolean isIE() {
 +        return browserDetails.isIE();
 +    }
 +
 +    public boolean isFirefox() {
 +        return browserDetails.isFirefox();
 +    }
 +
 +    public boolean isSafari() {
 +        return browserDetails.isSafari();
 +    }
 +
 +    public boolean isIE8() {
 +        return isIE() && getBrowserMajorVersion() == 8;
 +    }
 +
 +    public boolean isIE9() {
 +        return isIE() && getBrowserMajorVersion() == 9;
 +    }
 +
 +    public boolean isChrome() {
 +        return browserDetails.isChrome();
 +    }
 +
 +    public boolean isGecko() {
 +        return browserDetails.isGecko();
 +    }
 +
 +    public boolean isWebkit() {
 +        return browserDetails.isWebKit();
 +    }
 +
 +    /**
 +     * Returns the Gecko version if the browser is Gecko based. The Gecko
 +     * version for Firefox 2 is 1.8 and 1.9 for Firefox 3.
 +     * 
 +     * @return The Gecko version or -1 if the browser is not Gecko based
 +     */
 +    public float getGeckoVersion() {
 +        if (!browserDetails.isGecko()) {
 +            return -1;
 +        }
 +
 +        return browserDetails.getBrowserEngineVersion();
 +    }
 +
 +    /**
 +     * Returns the WebKit version if the browser is WebKit based. The WebKit
 +     * version returned is the major version e.g., 523.
 +     * 
 +     * @return The WebKit version or -1 if the browser is not WebKit based
 +     */
 +    public float getWebkitVersion() {
 +        if (!browserDetails.isWebKit()) {
 +            return -1;
 +        }
 +
 +        return browserDetails.getBrowserEngineVersion();
 +    }
 +
 +    public float getIEVersion() {
 +        if (!browserDetails.isIE()) {
 +            return -1;
 +        }
 +
 +        return getBrowserMajorVersion();
 +    }
 +
 +    public float getOperaVersion() {
 +        if (!browserDetails.isOpera()) {
 +            return -1;
 +        }
 +
 +        return getBrowserMajorVersion();
 +    }
 +
 +    public boolean isOpera() {
 +        return browserDetails.isOpera();
 +    }
 +
 +    public boolean isOpera10() {
 +        return browserDetails.isOpera() && getBrowserMajorVersion() == 10;
 +    }
 +
 +    public boolean isOpera11() {
 +        return browserDetails.isOpera() && getBrowserMajorVersion() == 11;
 +    }
 +
 +    public native static String getBrowserString()
 +    /*-{
 +      return $wnd.navigator.userAgent;
 +    }-*/;
 +
 +    public native int getScreenWidth()
 +    /*-{
 +      return $wnd.screen.width;
 +    }-*/;
 +
 +    public native int getScreenHeight()
 +    /*-{
 +      return $wnd.screen.height;
 +    }-*/;
 +
 +    /**
 +     * @return true if the browser runs on a touch based device.
 +     */
 +    public boolean isTouchDevice() {
 +        return touchDevice;
 +    }
 +
 +    /**
 +     * Indicates whether the browser might require juggling to properly update
 +     * sizes inside elements with overflow: auto.
 +     * 
 +     * @return <code>true</code> if the browser requires the workaround,
 +     *         otherwise <code>false</code>
 +     */
 +    public boolean requiresOverflowAutoFix() {
 +        return (getWebkitVersion() > 0 || getOperaVersion() >= 11)
 +                && Util.getNativeScrollbarSize() > 0;
 +    }
 +
 +    /**
 +     * Checks if the browser is run on iOS
 +     * 
 +     * @return true if the browser is run on iOS, false otherwise
 +     */
 +    public boolean isIOS() {
 +        return browserDetails.isIOS();
 +    }
 +
 +    /**
 +     * Checks if the browser is run on Android
 +     * 
 +     * @return true if the browser is run on Android, false otherwise
 +     */
 +    public boolean isAndroid() {
 +        return browserDetails.isAndroid();
 +    }
 +
 +    /**
 +     * Checks if the browser is capable of handling scrolling natively or if a
 +     * touch scroll helper is needed for scrolling.
 +     * 
 +     * @return true if browser needs a touch scroll helper, false if the browser
 +     *         can handle scrolling natively
 +     */
 +    public boolean requiresTouchScrollDelegate() {
 +        if (!isTouchDevice()) {
 +            return false;
 +        }
++        // TODO Should test other Android browsers, especially Chrome
 +        if (isAndroid() && isWebkit() && getWebkitVersion() >= 534) {
 +            return false;
 +        }
++        // iOS 6 Safari supports native scrolling; iOS 5 suffers from #8792
++        // TODO Should test other iOS browsers
++        if (isIOS() && isSafari() && getBrowserMajorVersion() >= 6) {
++            return false;
++        }
 +        return true;
 +    }
 +
 +    /**
 +     * Tests if this is an Android devices with a broken scrollTop
 +     * implementation
 +     * 
 +     * @return true if scrollTop cannot be trusted on this device, false
 +     *         otherwise
 +     */
 +    public boolean isAndroidWithBrokenScrollTop() {
 +        return isAndroid()
 +                && (getOperatingSystemMajorVersion() == 3 || getOperatingSystemMajorVersion() == 4);
 +    }
 +
 +    private int getOperatingSystemMajorVersion() {
 +        return browserDetails.getOperatingSystemMajorVersion();
 +    }
 +
 +    /**
 +     * Returns the browser major version e.g., 3 for Firefox 3.5, 4 for Chrome
 +     * 4, 8 for Internet Explorer 8.
 +     * <p>
 +     * Note that Internet Explorer 8 and newer will return the document mode so
 +     * IE8 rendering as IE7 will return 7.
 +     * </p>
 +     * 
 +     * @return The major version of the browser.
 +     */
 +    public int getBrowserMajorVersion() {
 +        return browserDetails.getBrowserMajorVersion();
 +    }
 +
 +    /**
 +     * Returns the browser minor version e.g., 5 for Firefox 3.5.
 +     * 
 +     * @see #getBrowserMajorVersion()
 +     * 
 +     * @return The minor version of the browser, or -1 if not known/parsed.
 +     */
 +    public int getBrowserMinorVersion() {
 +        return browserDetails.getBrowserMinorVersion();
 +    }
 +
 +    /**
 +     * Checks if the browser version is newer or equal to the given major+minor
 +     * version.
 +     * 
 +     * @param majorVersion
 +     *            The major version to check for
 +     * @param minorVersion
 +     *            The minor version to check for
 +     * @return true if the browser version is newer or equal to the given
 +     *         version
 +     */
 +    public boolean isBrowserVersionNewerOrEqual(int majorVersion,
 +            int minorVersion) {
 +        if (getBrowserMajorVersion() == majorVersion) {
 +            // Same major
 +            return (getBrowserMinorVersion() >= minorVersion);
 +        }
 +
 +        // Older or newer major
 +        return (getBrowserMajorVersion() > majorVersion);
 +    }
 +}
index dc40675e0af829dc83c68326bc50a2c2b51b9414,0000000000000000000000000000000000000000..9d519e3179e198c3b10fc133d85d9c458175fa3b
mode 100644,000000..100644
--- /dev/null
@@@ -1,1769 -1,0 +1,1780 @@@
-             Day day = (Day) event.getSource();
-             focusDay(day.getDate());
 +/*
 + * Copyright 2011 Vaadin Ltd.
 + * 
 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 + * use this file except in compliance with the License. You may obtain a copy of
 + * the License at
 + * 
 + * http://www.apache.org/licenses/LICENSE-2.0
 + * 
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 + * License for the specific language governing permissions and limitations under
 + * the License.
 + */
 +
 +package com.vaadin.client.ui.datefield;
 +
 +import java.util.Date;
 +import java.util.Iterator;
 +
 +import com.google.gwt.dom.client.Node;
 +import com.google.gwt.event.dom.client.BlurEvent;
 +import com.google.gwt.event.dom.client.BlurHandler;
 +import com.google.gwt.event.dom.client.ChangeEvent;
 +import com.google.gwt.event.dom.client.ChangeHandler;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +import com.google.gwt.event.dom.client.DomEvent;
 +import com.google.gwt.event.dom.client.FocusEvent;
 +import com.google.gwt.event.dom.client.FocusHandler;
 +import com.google.gwt.event.dom.client.KeyCodes;
 +import com.google.gwt.event.dom.client.KeyDownEvent;
 +import com.google.gwt.event.dom.client.KeyDownHandler;
 +import com.google.gwt.event.dom.client.KeyPressEvent;
 +import com.google.gwt.event.dom.client.KeyPressHandler;
 +import com.google.gwt.event.dom.client.MouseDownEvent;
 +import com.google.gwt.event.dom.client.MouseDownHandler;
 +import com.google.gwt.event.dom.client.MouseOutEvent;
 +import com.google.gwt.event.dom.client.MouseOutHandler;
 +import com.google.gwt.event.dom.client.MouseUpEvent;
 +import com.google.gwt.event.dom.client.MouseUpHandler;
 +import com.google.gwt.user.client.Element;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.ui.Button;
 +import com.google.gwt.user.client.ui.FlexTable;
 +import com.google.gwt.user.client.ui.FlowPanel;
 +import com.google.gwt.user.client.ui.InlineHTML;
 +import com.google.gwt.user.client.ui.ListBox;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.DateTimeService;
 +import com.vaadin.client.Util;
 +import com.vaadin.client.VConsole;
 +import com.vaadin.client.ui.FocusableFlexTable;
 +import com.vaadin.client.ui.SubPartAware;
 +import com.vaadin.client.ui.label.VLabel;
 +import com.vaadin.client.ui.nativeselect.VNativeSelect;
 +
 +@SuppressWarnings("deprecation")
 +public class VCalendarPanel extends FocusableFlexTable implements
 +        KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler,
 +        MouseUpHandler, BlurHandler, FocusHandler, SubPartAware {
 +
 +    public interface SubmitListener {
 +
 +        /**
 +         * Called when calendar user triggers a submitting operation in calendar
 +         * panel. Eg. clicking on day or hitting enter.
 +         */
 +        void onSubmit();
 +
 +        /**
 +         * On eg. ESC key.
 +         */
 +        void onCancel();
 +    }
 +
 +    /**
 +     * Blur listener that listens to blur event from the panel
 +     */
 +    public interface FocusOutListener {
 +        /**
 +         * @return true if the calendar panel is not used after focus moves out
 +         */
 +        boolean onFocusOut(DomEvent<?> event);
 +    }
 +
 +    /**
 +     * FocusChangeListener is notified when the panel changes its _focused_
 +     * value.
 +     */
 +    public interface FocusChangeListener {
 +        void focusChanged(Date focusedDate);
 +    }
 +
 +    /**
 +     * Dispatches an event when the panel when time is changed
 +     */
 +    public interface TimeChangeListener {
 +
 +        void changed(int hour, int min, int sec, int msec);
 +    }
 +
 +    /**
 +     * Represents a Date button in the calendar
 +     */
 +    private class VEventButton extends Button {
 +        public VEventButton() {
 +            addMouseDownHandler(VCalendarPanel.this);
 +            addMouseOutHandler(VCalendarPanel.this);
 +            addMouseUpHandler(VCalendarPanel.this);
 +        }
 +    }
 +
 +    private static final String CN_FOCUSED = "focused";
 +
 +    private static final String CN_TODAY = "today";
 +
 +    private static final String CN_SELECTED = "selected";
 +
 +    private static final String CN_OFFMONTH = "offmonth";
 +
 +    /**
 +     * Represents a click handler for when a user selects a value by using the
 +     * mouse
 +     */
 +    private ClickHandler dayClickHandler = new ClickHandler() {
 +        /*
 +         * (non-Javadoc)
 +         * 
 +         * @see
 +         * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
 +         * .event.dom.client.ClickEvent)
 +         */
 +        @Override
 +        public void onClick(ClickEvent event) {
-                 focusedDate.getMonth()) : "";
-         final int year = focusedDate.getYear() + 1900;
++            Date newDate = ((Day) event.getSource()).getDate();
++            if (newDate.getMonth() != displayedMonth.getMonth()
++                    || newDate.getYear() != displayedMonth.getYear()) {
++                // If an off-month date was clicked, we must change the
++                // displayed month and re-render the calendar (#8931)
++                displayedMonth.setMonth(newDate.getMonth());
++                displayedMonth.setYear(newDate.getYear());
++                renderCalendar();
++            }
++            focusDay(newDate);
 +            selectFocused();
 +            onSubmit();
 +        }
 +    };
 +
 +    private VEventButton prevYear;
 +
 +    private VEventButton nextYear;
 +
 +    private VEventButton prevMonth;
 +
 +    private VEventButton nextMonth;
 +
 +    private VTime time;
 +
 +    private FlexTable days = new FlexTable();
 +
 +    private int resolution = VDateField.RESOLUTION_YEAR;
 +
 +    private int focusedRow;
 +
 +    private Timer mouseTimer;
 +
 +    private Date value;
 +
 +    private boolean enabled = true;
 +
 +    private boolean readonly = false;
 +
 +    private DateTimeService dateTimeService;
 +
 +    private boolean showISOWeekNumbers;
 +
 +    private Date displayedMonth;
 +
 +    private Date focusedDate;
 +
 +    private Day selectedDay;
 +
 +    private Day focusedDay;
 +
 +    private FocusOutListener focusOutListener;
 +
 +    private SubmitListener submitListener;
 +
 +    private FocusChangeListener focusChangeListener;
 +
 +    private TimeChangeListener timeChangeListener;
 +
 +    private boolean hasFocus = false;
 +
 +    public VCalendarPanel() {
 +
 +        setStyleName(VDateField.CLASSNAME + "-calendarpanel");
 +
 +        /*
 +         * Firefox auto-repeat works correctly only if we use a key press
 +         * handler, other browsers handle it correctly when using a key down
 +         * handler
 +         */
 +        if (BrowserInfo.get().isGecko()) {
 +            addKeyPressHandler(this);
 +        } else {
 +            addKeyDownHandler(this);
 +        }
 +        addFocusHandler(this);
 +        addBlurHandler(this);
 +
 +    }
 +
 +    /**
 +     * Sets the focus to given date in the current view. Used when moving in the
 +     * calendar with the keyboard.
 +     * 
 +     * @param date
 +     *            A Date representing the day of month to be focused. Must be
 +     *            one of the days currently visible.
 +     */
 +    private void focusDay(Date date) {
 +        // Only used when calender body is present
 +        if (resolution > VDateField.RESOLUTION_MONTH) {
 +            if (focusedDay != null) {
 +                focusedDay.removeStyleDependentName(CN_FOCUSED);
 +            }
 +
 +            if (date != null && focusedDate != null) {
 +                focusedDate.setTime(date.getTime());
 +                int rowCount = days.getRowCount();
 +                for (int i = 0; i < rowCount; i++) {
 +                    int cellCount = days.getCellCount(i);
 +                    for (int j = 0; j < cellCount; j++) {
 +                        Widget widget = days.getWidget(i, j);
 +                        if (widget != null && widget instanceof Day) {
 +                            Day curday = (Day) widget;
 +                            if (curday.getDate().equals(date)) {
 +                                curday.addStyleDependentName(CN_FOCUSED);
 +                                focusedDay = curday;
 +                                focusedRow = i;
 +                                return;
 +                            }
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Sets the selection highlight to a given day in the current view
 +     * 
 +     * @param date
 +     *            A Date representing the day of month to be selected. Must be
 +     *            one of the days currently visible.
 +     * 
 +     */
 +    private void selectDate(Date date) {
 +        if (selectedDay != null) {
 +            selectedDay.removeStyleDependentName(CN_SELECTED);
 +        }
 +
 +        int rowCount = days.getRowCount();
 +        for (int i = 0; i < rowCount; i++) {
 +            int cellCount = days.getCellCount(i);
 +            for (int j = 0; j < cellCount; j++) {
 +                Widget widget = days.getWidget(i, j);
 +                if (widget != null && widget instanceof Day) {
 +                    Day curday = (Day) widget;
 +                    if (curday.getDate().equals(date)) {
 +                        curday.addStyleDependentName(CN_SELECTED);
 +                        selectedDay = curday;
 +                        return;
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Updates year, month, day from focusedDate to value
 +     */
 +    private void selectFocused() {
 +        if (focusedDate != null) {
 +            if (value == null) {
 +                // No previously selected value (set to null on server side).
 +                // Create a new date using current date and time
 +                value = new Date();
 +            }
 +            /*
 +             * #5594 set Date (day) to 1 in order to prevent any kind of
 +             * wrapping of months when later setting the month. (e.g. 31 ->
 +             * month with 30 days -> wraps to the 1st of the following month,
 +             * e.g. 31st of May -> 31st of April = 1st of May)
 +             */
 +            value.setDate(1);
 +            if (value.getYear() != focusedDate.getYear()) {
 +                value.setYear(focusedDate.getYear());
 +            }
 +            if (value.getMonth() != focusedDate.getMonth()) {
 +                value.setMonth(focusedDate.getMonth());
 +            }
 +            if (value.getDate() != focusedDate.getDate()) {
 +            }
 +            // We always need to set the date, even if it hasn't changed, since
 +            // it was forced to 1 above.
 +            value.setDate(focusedDate.getDate());
 +
 +            selectDate(focusedDate);
 +        } else {
 +            VConsole.log("Trying to select a the focused date which is NULL!");
 +        }
 +    }
 +
 +    protected boolean onValueChange() {
 +        return false;
 +    }
 +
 +    public int getResolution() {
 +        return resolution;
 +    }
 +
 +    public void setResolution(int resolution) {
 +        this.resolution = resolution;
 +        if (time != null) {
 +            time.removeFromParent();
 +            time = null;
 +        }
 +    }
 +
 +    private boolean isReadonly() {
 +        return readonly;
 +    }
 +
 +    private boolean isEnabled() {
 +        return enabled;
 +    }
 +
 +    private void clearCalendarBody(boolean remove) {
 +        if (!remove) {
 +            // Leave the cells in place but clear their contents
 +
 +            // This has the side effect of ensuring that the calendar always
 +            // contain 7 rows.
 +            for (int row = 1; row < 7; row++) {
 +                for (int col = 0; col < 8; col++) {
 +                    days.setHTML(row, col, "&nbsp;");
 +                }
 +            }
 +        } else if (getRowCount() > 1) {
 +            removeRow(1);
 +            days.clear();
 +        }
 +    }
 +
 +    /**
 +     * Builds the top buttons and current month and year header.
 +     * 
 +     * @param needsMonth
 +     *            Should the month buttons be visible?
 +     */
 +    private void buildCalendarHeader(boolean needsMonth) {
 +
 +        getRowFormatter().addStyleName(0,
 +                VDateField.CLASSNAME + "-calendarpanel-header");
 +
 +        if (prevMonth == null && needsMonth) {
 +            prevMonth = new VEventButton();
 +            prevMonth.setHTML("&lsaquo;");
 +            prevMonth.setStyleName("v-button-prevmonth");
 +            prevMonth.setTabIndex(-1);
 +            nextMonth = new VEventButton();
 +            nextMonth.setHTML("&rsaquo;");
 +            nextMonth.setStyleName("v-button-nextmonth");
 +            nextMonth.setTabIndex(-1);
 +            getFlexCellFormatter().setStyleName(0, 3,
 +                    VDateField.CLASSNAME + "-calendarpanel-nextmonth");
 +            getFlexCellFormatter().setStyleName(0, 1,
 +                    VDateField.CLASSNAME + "-calendarpanel-prevmonth");
 +
 +            setWidget(0, 3, nextMonth);
 +            setWidget(0, 1, prevMonth);
 +        } else if (prevMonth != null && !needsMonth) {
 +            // Remove month traverse buttons
 +            remove(prevMonth);
 +            remove(nextMonth);
 +            prevMonth = null;
 +            nextMonth = null;
 +        }
 +
 +        if (prevYear == null) {
 +            prevYear = new VEventButton();
 +            prevYear.setHTML("&laquo;");
 +            prevYear.setStyleName("v-button-prevyear");
 +            prevYear.setTabIndex(-1);
 +            nextYear = new VEventButton();
 +            nextYear.setHTML("&raquo;");
 +            nextYear.setStyleName("v-button-nextyear");
 +            nextYear.setTabIndex(-1);
 +            setWidget(0, 0, prevYear);
 +            setWidget(0, 4, nextYear);
 +            getFlexCellFormatter().setStyleName(0, 0,
 +                    VDateField.CLASSNAME + "-calendarpanel-prevyear");
 +            getFlexCellFormatter().setStyleName(0, 4,
 +                    VDateField.CLASSNAME + "-calendarpanel-nextyear");
 +        }
 +
 +        final String monthName = needsMonth ? getDateTimeService().getMonth(
-         if (focusedDate.getMonth() == oldMonth) {
++                displayedMonth.getMonth()) : "";
++        final int year = displayedMonth.getYear() + 1900;
 +        getFlexCellFormatter().setStyleName(0, 2,
 +                VDateField.CLASSNAME + "-calendarpanel-month");
 +        setHTML(0, 2, "<span class=\"" + VDateField.CLASSNAME
 +                + "-calendarpanel-month\">" + monthName + " " + year
 +                + "</span>");
 +    }
 +
 +    private DateTimeService getDateTimeService() {
 +        return dateTimeService;
 +    }
 +
 +    public void setDateTimeService(DateTimeService dateTimeService) {
 +        this.dateTimeService = dateTimeService;
 +    }
 +
 +    /**
 +     * Returns whether ISO 8601 week numbers should be shown in the value
 +     * selector or not. ISO 8601 defines that a week always starts with a Monday
 +     * so the week numbers are only shown if this is the case.
 +     * 
 +     * @return true if week number should be shown, false otherwise
 +     */
 +    public boolean isShowISOWeekNumbers() {
 +        return showISOWeekNumbers;
 +    }
 +
 +    public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
 +        this.showISOWeekNumbers = showISOWeekNumbers;
 +    }
 +
 +    /**
 +     * Builds the day and time selectors of the calendar.
 +     */
 +    private void buildCalendarBody() {
 +
 +        final int weekColumn = 0;
 +        final int firstWeekdayColumn = 1;
 +        final int headerRow = 0;
 +
 +        setWidget(1, 0, days);
 +        setCellPadding(0);
 +        setCellSpacing(0);
 +        getFlexCellFormatter().setColSpan(1, 0, 5);
 +        getFlexCellFormatter().setStyleName(1, 0,
 +                VDateField.CLASSNAME + "-calendarpanel-body");
 +
 +        days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
 +                "v-week");
 +        days.setHTML(headerRow, weekColumn, "<strong></strong>");
 +        // Hide the week column if week numbers are not to be displayed.
 +        days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
 +                isShowISOWeekNumbers());
 +
 +        days.getRowFormatter().setStyleName(headerRow,
 +                VDateField.CLASSNAME + "-calendarpanel-weekdays");
 +
 +        if (isShowISOWeekNumbers()) {
 +            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
 +                    "v-first");
 +            days.getFlexCellFormatter().setStyleName(headerRow,
 +                    firstWeekdayColumn, "");
 +            days.getRowFormatter().addStyleName(headerRow,
 +                    VDateField.CLASSNAME + "-calendarpanel-weeknumbers");
 +        } else {
 +            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
 +            days.getFlexCellFormatter().setStyleName(headerRow,
 +                    firstWeekdayColumn, "v-first");
 +        }
 +
 +        days.getFlexCellFormatter().setStyleName(headerRow,
 +                firstWeekdayColumn + 6, "v-last");
 +
 +        // Print weekday names
 +        final int firstDay = getDateTimeService().getFirstDayOfWeek();
 +        for (int i = 0; i < 7; i++) {
 +            int day = i + firstDay;
 +            if (day > 6) {
 +                day = 0;
 +            }
 +            if (getResolution() > VDateField.RESOLUTION_MONTH) {
 +                days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
 +                        + getDateTimeService().getShortDay(day) + "</strong>");
 +            } else {
 +                days.setHTML(headerRow, firstWeekdayColumn + i, "");
 +            }
 +        }
 +
 +        // Zero out hours, minutes, seconds, and milliseconds to compare dates
 +        // without time part
 +        final Date tmp = new Date();
 +        final Date today = new Date(tmp.getYear(), tmp.getMonth(),
 +                tmp.getDate());
 +
 +        final Date selectedDate = value == null ? null : new Date(
 +                value.getYear(), value.getMonth(), value.getDate());
 +
 +        final int startWeekDay = getDateTimeService().getStartWeekDay(
 +                displayedMonth);
 +        final Date curr = (Date) displayedMonth.clone();
 +        // Start from the first day of the week that at least partially belongs
 +        // to the current month
 +        curr.setDate(1 - startWeekDay);
 +
 +        // No month has more than 6 weeks so 6 is a safe maximum for rows.
 +        for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
 +            for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
 +
 +                // Actually write the day of month
 +                Day day = new Day((Date) curr.clone());
 +
 +                if (curr.equals(selectedDate)) {
 +                    day.addStyleDependentName(CN_SELECTED);
 +                    selectedDay = day;
 +                }
 +                if (curr.equals(today)) {
 +                    day.addStyleDependentName(CN_TODAY);
 +                }
 +                if (curr.equals(focusedDate)) {
 +                    focusedDay = day;
 +                    focusedRow = weekOfMonth;
 +                    if (hasFocus) {
 +                        day.addStyleDependentName(CN_FOCUSED);
 +                    }
 +                }
 +                if (curr.getMonth() != displayedMonth.getMonth()) {
 +                    day.addStyleDependentName(CN_OFFMONTH);
 +                }
 +
 +                days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
 +
 +                // ISO week numbers if requested
 +                days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
 +                        isShowISOWeekNumbers());
 +                if (isShowISOWeekNumbers()) {
 +                    final String baseCssClass = VDateField.CLASSNAME
 +                            + "-calendarpanel-weeknumber";
 +                    String weekCssClass = baseCssClass;
 +
 +                    int weekNumber = DateTimeService.getISOWeekNumber(curr);
 +
 +                    days.setHTML(weekOfMonth, 0, "<span class=\""
 +                            + weekCssClass + "\"" + ">" + weekNumber
 +                            + "</span>");
 +                }
 +                curr.setDate(curr.getDate() + 1);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Do we need the time selector
 +     * 
 +     * @return True if it is required
 +     */
 +    private boolean isTimeSelectorNeeded() {
 +        return getResolution() > VDateField.RESOLUTION_DAY;
 +    }
 +
 +    /**
 +     * Updates the calendar and text field with the selected dates.
 +     */
 +    public void renderCalendar() {
 +        if (focusedDate == null) {
 +            Date now = new Date();
 +            // focusedDate must have zero hours, mins, secs, millisecs
 +            focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
 +            displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
 +        }
 +
 +        if (getResolution() <= VDateField.RESOLUTION_MONTH
 +                && focusChangeListener != null) {
 +            focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
 +        }
 +
 +        final boolean needsMonth = getResolution() > VDateField.RESOLUTION_YEAR;
 +        boolean needsBody = getResolution() >= VDateField.RESOLUTION_DAY;
 +        buildCalendarHeader(needsMonth);
 +        clearCalendarBody(!needsBody);
 +        if (needsBody) {
 +            buildCalendarBody();
 +        }
 +
 +        if (isTimeSelectorNeeded() && time == null) {
 +            time = new VTime();
 +            setWidget(2, 0, time);
 +            getFlexCellFormatter().setColSpan(2, 0, 5);
 +            getFlexCellFormatter().setStyleName(2, 0,
 +                    VDateField.CLASSNAME + "-calendarpanel-time");
 +        } else if (isTimeSelectorNeeded()) {
 +            time.updateTimes();
 +        } else if (time != null) {
 +            remove(time);
 +        }
 +    }
 +
 +    /**
 +     * Moves the focus forward the given number of days.
 +     */
 +    private void focusNextDay(int days) {
 +        int oldMonth = focusedDate.getMonth();
++        int oldYear = focusedDate.getYear();
 +        focusedDate.setDate(focusedDate.getDate() + days);
 +
++        if (focusedDate.getMonth() == oldMonth
++                && focusedDate.getYear() == oldYear) {
 +            // Month did not change, only move the selection
 +            focusDay(focusedDate);
 +        } else {
 +            // If the month changed we need to re-render the calendar
 +            displayedMonth.setMonth(focusedDate.getMonth());
++            displayedMonth.setYear(focusedDate.getYear());
 +            renderCalendar();
 +        }
 +    }
 +
 +    /**
 +     * Moves the focus backward the given number of days.
 +     */
 +    private void focusPreviousDay(int days) {
 +        focusNextDay(-days);
 +    }
 +
 +    /**
 +     * Selects the next month
 +     */
 +    private void focusNextMonth() {
 +
 +        int currentMonth = focusedDate.getMonth();
 +        focusedDate.setMonth(currentMonth + 1);
 +        int requestedMonth = (currentMonth + 1) % 12;
 +
 +        /*
 +         * If the selected value was e.g. 31.3 the new value would be 31.4 but
 +         * this value is invalid so the new value will be 1.5. This is taken
 +         * care of by decreasing the value until we have the correct month.
 +         */
 +        while (focusedDate.getMonth() != requestedMonth) {
 +            focusedDate.setDate(focusedDate.getDate() - 1);
 +        }
 +        displayedMonth.setMonth(displayedMonth.getMonth() + 1);
 +
 +        renderCalendar();
 +    }
 +
 +    /**
 +     * Selects the previous month
 +     */
 +    private void focusPreviousMonth() {
 +        int currentMonth = focusedDate.getMonth();
 +        focusedDate.setMonth(currentMonth - 1);
 +
 +        /*
 +         * If the selected value was e.g. 31.12 the new value would be 31.11 but
 +         * this value is invalid so the new value will be 1.12. This is taken
 +         * care of by decreasing the value until we have the correct month.
 +         */
 +        while (focusedDate.getMonth() == currentMonth) {
 +            focusedDate.setDate(focusedDate.getDate() - 1);
 +        }
 +        displayedMonth.setMonth(displayedMonth.getMonth() - 1);
 +
 +        renderCalendar();
 +    }
 +
 +    /**
 +     * Selects the previous year
 +     */
 +    private void focusPreviousYear(int years) {
 +        int currentMonth = focusedDate.getMonth();
 +        focusedDate.setYear(focusedDate.getYear() - years);
 +        displayedMonth.setYear(displayedMonth.getYear() - years);
 +        /*
 +         * If the focused date was a leap day (Feb 29), the new date becomes Mar
 +         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
 +         */
 +        if (focusedDate.getMonth() != currentMonth) {
 +            focusedDate.setDate(0);
 +        }
 +        renderCalendar();
 +    }
 +
 +    /**
 +     * Selects the next year
 +     */
 +    private void focusNextYear(int years) {
 +        int currentMonth = focusedDate.getMonth();
 +        focusedDate.setYear(focusedDate.getYear() + years);
 +        displayedMonth.setYear(displayedMonth.getYear() + years);
 +        /*
 +         * If the focused date was a leap day (Feb 29), the new date becomes Mar
 +         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
 +         */
 +        if (focusedDate.getMonth() != currentMonth) {
 +            focusedDate.setDate(0);
 +        }
 +        renderCalendar();
 +    }
 +
 +    /**
 +     * Handles a user click on the component
 +     * 
 +     * @param sender
 +     *            The component that was clicked
 +     * @param updateVariable
 +     *            Should the value field be updated
 +     * 
 +     */
 +    private void processClickEvent(Widget sender) {
 +        if (!isEnabled() || isReadonly()) {
 +            return;
 +        }
 +        if (sender == prevYear) {
 +            focusPreviousYear(1);
 +        } else if (sender == nextYear) {
 +            focusNextYear(1);
 +        } else if (sender == prevMonth) {
 +            focusPreviousMonth();
 +        } else if (sender == nextMonth) {
 +            focusNextMonth();
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
 +     * .event.dom.client.KeyDownEvent)
 +     */
 +    @Override
 +    public void onKeyDown(KeyDownEvent event) {
 +        handleKeyPress(event);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
 +     * .gwt.event.dom.client.KeyPressEvent)
 +     */
 +    @Override
 +    public void onKeyPress(KeyPressEvent event) {
 +        handleKeyPress(event);
 +    }
 +
 +    /**
 +     * Handles the keypress from both the onKeyPress event and the onKeyDown
 +     * event
 +     * 
 +     * @param event
 +     *            The keydown/keypress event
 +     */
 +    private void handleKeyPress(DomEvent<?> event) {
 +        if (time != null
 +                && time.getElement().isOrHasChild(
 +                        (Node) event.getNativeEvent().getEventTarget().cast())) {
 +            int nativeKeyCode = event.getNativeEvent().getKeyCode();
 +            if (nativeKeyCode == getSelectKey()) {
 +                onSubmit(); // submit happens if enter key hit down on listboxes
 +                event.preventDefault();
 +                event.stopPropagation();
 +            }
 +            return;
 +        }
 +
 +        // Check tabs
 +        int keycode = event.getNativeEvent().getKeyCode();
 +        if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) {
 +            if (onTabOut(event)) {
 +                return;
 +            }
 +        }
 +
 +        // Handle the navigation
 +        if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey()
 +                || event.getNativeEvent().getMetaKey(), event.getNativeEvent()
 +                .getShiftKey())) {
 +            event.preventDefault();
 +        }
 +
 +    }
 +
 +    /**
 +     * Notifies submit-listeners of a submit event
 +     */
 +    private void onSubmit() {
 +        if (getSubmitListener() != null) {
 +            getSubmitListener().onSubmit();
 +        }
 +    }
 +
 +    /**
 +     * Notifies submit-listeners of a cancel event
 +     */
 +    private void onCancel() {
 +        if (getSubmitListener() != null) {
 +            getSubmitListener().onCancel();
 +        }
 +    }
 +
 +    /**
 +     * Handles the keyboard navigation when the resolution is set to years.
 +     * 
 +     * @param keycode
 +     *            The keycode to process
 +     * @param ctrl
 +     *            Is ctrl pressed?
 +     * @param shift
 +     *            is shift pressed
 +     * @return Returns true if the keycode was processed, else false
 +     */
 +    protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
 +            boolean shift) {
 +
 +        // Ctrl and Shift selection not supported
 +        if (ctrl || shift) {
 +            return false;
 +        }
 +
 +        else if (keycode == getPreviousKey()) {
 +            focusNextYear(10); // Add 10 years
 +            return true;
 +        }
 +
 +        else if (keycode == getForwardKey()) {
 +            focusNextYear(1); // Add 1 year
 +            return true;
 +        }
 +
 +        else if (keycode == getNextKey()) {
 +            focusPreviousYear(10); // Subtract 10 years
 +            return true;
 +        }
 +
 +        else if (keycode == getBackwardKey()) {
 +            focusPreviousYear(1); // Subtract 1 year
 +            return true;
 +
 +        } else if (keycode == getSelectKey()) {
 +            value = (Date) focusedDate.clone();
 +            onSubmit();
 +            return true;
 +
 +        } else if (keycode == getResetKey()) {
 +            // Restore showing value the selected value
 +            focusedDate.setTime(value.getTime());
 +            renderCalendar();
 +            return true;
 +
 +        } else if (keycode == getCloseKey()) {
 +            // TODO fire listener, on users responsibility??
 +
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * Handle the keyboard navigation when the resolution is set to MONTH
 +     * 
 +     * @param keycode
 +     *            The keycode to handle
 +     * @param ctrl
 +     *            Was the ctrl key pressed?
 +     * @param shift
 +     *            Was the shift key pressed?
 +     * @return
 +     */
 +    protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
 +            boolean shift) {
 +
 +        // Ctrl selection not supported
 +        if (ctrl) {
 +            return false;
 +
 +        } else if (keycode == getPreviousKey()) {
 +            focusNextYear(1); // Add 1 year
 +            return true;
 +
 +        } else if (keycode == getForwardKey()) {
 +            focusNextMonth(); // Add 1 month
 +            return true;
 +
 +        } else if (keycode == getNextKey()) {
 +            focusPreviousYear(1); // Subtract 1 year
 +            return true;
 +
 +        } else if (keycode == getBackwardKey()) {
 +            focusPreviousMonth(); // Subtract 1 month
 +            return true;
 +
 +        } else if (keycode == getSelectKey()) {
 +            value = (Date) focusedDate.clone();
 +            onSubmit();
 +            return true;
 +
 +        } else if (keycode == getResetKey()) {
 +            // Restore showing value the selected value
 +            focusedDate.setTime(value.getTime());
 +            renderCalendar();
 +            return true;
 +
 +        } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
 +
 +            // TODO fire close event
 +
 +            return true;
 +        }
 +
 +        return false;
 +    }
 +
 +    /**
 +     * Handle keyboard navigation what the resolution is set to DAY
 +     * 
 +     * @param keycode
 +     *            The keycode to handle
 +     * @param ctrl
 +     *            Was the ctrl key pressed?
 +     * @param shift
 +     *            Was the shift key pressed?
 +     * @return Return true if the key press was handled by the method, else
 +     *         return false.
 +     */
 +    protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
 +            boolean shift) {
 +
 +        // Ctrl key is not in use
 +        if (ctrl) {
 +            return false;
 +        }
 +
 +        /*
 +         * Jumps to the next day.
 +         */
 +        if (keycode == getForwardKey() && !shift) {
 +            focusNextDay(1);
 +            return true;
 +
 +            /*
 +             * Jumps to the previous day
 +             */
 +        } else if (keycode == getBackwardKey() && !shift) {
 +            focusPreviousDay(1);
 +            return true;
 +
 +            /*
 +             * Jumps one week forward in the calendar
 +             */
 +        } else if (keycode == getNextKey() && !shift) {
 +            focusNextDay(7);
 +            return true;
 +
 +            /*
 +             * Jumps one week back in the calendar
 +             */
 +        } else if (keycode == getPreviousKey() && !shift) {
 +            focusPreviousDay(7);
 +            return true;
 +
 +            /*
 +             * Selects the value that is chosen
 +             */
 +        } else if (keycode == getSelectKey() && !shift) {
 +            selectFocused();
 +            onSubmit(); // submit
 +            return true;
 +
 +        } else if (keycode == getCloseKey()) {
 +            onCancel();
 +            // TODO close event
 +
 +            return true;
 +
 +            /*
 +             * Jumps to the next month
 +             */
 +        } else if (shift && keycode == getForwardKey()) {
 +            focusNextMonth();
 +            return true;
 +
 +            /*
 +             * Jumps to the previous month
 +             */
 +        } else if (shift && keycode == getBackwardKey()) {
 +            focusPreviousMonth();
 +            return true;
 +
 +            /*
 +             * Jumps to the next year
 +             */
 +        } else if (shift && keycode == getPreviousKey()) {
 +            focusNextYear(1);
 +            return true;
 +
 +            /*
 +             * Jumps to the previous year
 +             */
 +        } else if (shift && keycode == getNextKey()) {
 +            focusPreviousYear(1);
 +            return true;
 +
 +            /*
 +             * Resets the selection
 +             */
 +        } else if (keycode == getResetKey() && !shift) {
 +            // Restore showing value the selected value
 +            focusedDate = new Date(value.getYear(), value.getMonth(),
 +                    value.getDate());
 +            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
 +            renderCalendar();
 +            return true;
 +        }
 +
 +        return false;
 +    }
 +
 +    /**
 +     * Handles the keyboard navigation
 +     * 
 +     * @param keycode
 +     *            The key code that was pressed
 +     * @param ctrl
 +     *            Was the ctrl key pressed
 +     * @param shift
 +     *            Was the shift key pressed
 +     * @return Return true if key press was handled by the component, else
 +     *         return false
 +     */
 +    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
 +        if (!isEnabled() || isReadonly()) {
 +            return false;
 +        }
 +
 +        else if (resolution == VDateField.RESOLUTION_YEAR) {
 +            return handleNavigationYearMode(keycode, ctrl, shift);
 +        }
 +
 +        else if (resolution == VDateField.RESOLUTION_MONTH) {
 +            return handleNavigationMonthMode(keycode, ctrl, shift);
 +        }
 +
 +        else if (resolution == VDateField.RESOLUTION_DAY) {
 +            return handleNavigationDayMode(keycode, ctrl, shift);
 +        }
 +
 +        else {
 +            return handleNavigationDayMode(keycode, ctrl, shift);
 +        }
 +
 +    }
 +
 +    /**
 +     * Returns the reset key which will reset the calendar to the previous
 +     * selection. By default this is backspace but it can be overriden to change
 +     * the key to whatever you want.
 +     * 
 +     * @return
 +     */
 +    protected int getResetKey() {
 +        return KeyCodes.KEY_BACKSPACE;
 +    }
 +
 +    /**
 +     * Returns the select key which selects the value. By default this is the
 +     * enter key but it can be changed to whatever you like by overriding this
 +     * method.
 +     * 
 +     * @return
 +     */
 +    protected int getSelectKey() {
 +        return KeyCodes.KEY_ENTER;
 +    }
 +
 +    /**
 +     * Returns the key that closes the popup window if this is a VPopopCalendar.
 +     * Else this does nothing. By default this is the Escape key but you can
 +     * change the key to whatever you want by overriding this method.
 +     * 
 +     * @return
 +     */
 +    protected int getCloseKey() {
 +        return KeyCodes.KEY_ESCAPE;
 +    }
 +
 +    /**
 +     * The key that selects the next day in the calendar. By default this is the
 +     * right arrow key but by overriding this method it can be changed to
 +     * whatever you like.
 +     * 
 +     * @return
 +     */
 +    protected int getForwardKey() {
 +        return KeyCodes.KEY_RIGHT;
 +    }
 +
 +    /**
 +     * The key that selects the previous day in the calendar. By default this is
 +     * the left arrow key but by overriding this method it can be changed to
 +     * whatever you like.
 +     * 
 +     * @return
 +     */
 +    protected int getBackwardKey() {
 +        return KeyCodes.KEY_LEFT;
 +    }
 +
 +    /**
 +     * The key that selects the next week in the calendar. By default this is
 +     * the down arrow key but by overriding this method it can be changed to
 +     * whatever you like.
 +     * 
 +     * @return
 +     */
 +    protected int getNextKey() {
 +        return KeyCodes.KEY_DOWN;
 +    }
 +
 +    /**
 +     * The key that selects the previous week in the calendar. By default this
 +     * is the up arrow key but by overriding this method it can be changed to
 +     * whatever you like.
 +     * 
 +     * @return
 +     */
 +    protected int getPreviousKey() {
 +        return KeyCodes.KEY_UP;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
 +     * .gwt.event.dom.client.MouseOutEvent)
 +     */
 +    @Override
 +    public void onMouseOut(MouseOutEvent event) {
 +        if (mouseTimer != null) {
 +            mouseTimer.cancel();
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
 +     * .gwt.event.dom.client.MouseDownEvent)
 +     */
 +    @Override
 +    public void onMouseDown(MouseDownEvent event) {
 +        // Allow user to click-n-hold for fast-forward or fast-rewind.
 +        // Timer is first used for a 500ms delay after mousedown. After that has
 +        // elapsed, another timer is triggered to go off every 150ms. Both
 +        // timers are cancelled on mouseup or mouseout.
 +        if (event.getSource() instanceof VEventButton) {
 +            final VEventButton sender = (VEventButton) event.getSource();
 +            processClickEvent(sender);
 +            mouseTimer = new Timer() {
 +                @Override
 +                public void run() {
 +                    mouseTimer = new Timer() {
 +                        @Override
 +                        public void run() {
 +                            processClickEvent(sender);
 +                        }
 +                    };
 +                    mouseTimer.scheduleRepeating(150);
 +                }
 +            };
 +            mouseTimer.schedule(500);
 +        }
 +
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
 +     * .event.dom.client.MouseUpEvent)
 +     */
 +    @Override
 +    public void onMouseUp(MouseUpEvent event) {
 +        if (mouseTimer != null) {
 +            mouseTimer.cancel();
 +        }
 +    }
 +
 +    /**
 +     * Sets the data of the Panel.
 +     * 
 +     * @param currentDate
 +     *            The date to set
 +     */
 +    public void setDate(Date currentDate) {
 +
 +        // Check that we are not re-rendering an already active date
 +        if (currentDate == value && currentDate != null) {
 +            return;
 +        }
 +
 +        Date oldDisplayedMonth = displayedMonth;
 +        value = currentDate;
 +
 +        if (value == null) {
 +            focusedDate = displayedMonth = null;
 +        } else {
 +            focusedDate = new Date(value.getYear(), value.getMonth(),
 +                    value.getDate());
 +            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
 +        }
 +
 +        // Re-render calendar if the displayed month is changed,
 +        // or if a time selector is needed but does not exist.
 +        if ((isTimeSelectorNeeded() && time == null)
 +                || oldDisplayedMonth == null || value == null
 +                || oldDisplayedMonth.getYear() != value.getYear()
 +                || oldDisplayedMonth.getMonth() != value.getMonth()) {
 +            renderCalendar();
 +        } else {
 +            focusDay(focusedDate);
 +            selectFocused();
 +            if (isTimeSelectorNeeded()) {
 +                time.updateTimes();
 +            }
 +        }
 +
 +        if (!hasFocus) {
 +            focusDay(null);
 +        }
 +    }
 +
 +    /**
 +     * TimeSelector is a widget consisting of list boxes that modifie the Date
 +     * object that is given for.
 +     * 
 +     */
 +    public class VTime extends FlowPanel implements ChangeHandler {
 +
 +        private ListBox hours;
 +
 +        private ListBox mins;
 +
 +        private ListBox sec;
 +
 +        private ListBox ampm;
 +
 +        /**
 +         * Constructor
 +         */
 +        public VTime() {
 +            super();
 +            setStyleName(VDateField.CLASSNAME + "-time");
 +            buildTime();
 +        }
 +
 +        private ListBox createListBox() {
 +            ListBox lb = new ListBox();
 +            lb.setStyleName(VNativeSelect.CLASSNAME);
 +            lb.addChangeHandler(this);
 +            lb.addBlurHandler(VCalendarPanel.this);
 +            lb.addFocusHandler(VCalendarPanel.this);
 +            return lb;
 +        }
 +
 +        /**
 +         * Constructs the ListBoxes and updates their value
 +         * 
 +         * @param redraw
 +         *            Should new instances of the listboxes be created
 +         */
 +        private void buildTime() {
 +            clear();
 +
 +            hours = createListBox();
 +            if (getDateTimeService().isTwelveHourClock()) {
 +                hours.addItem("12");
 +                for (int i = 1; i < 12; i++) {
 +                    hours.addItem((i < 10) ? "0" + i : "" + i);
 +                }
 +            } else {
 +                for (int i = 0; i < 24; i++) {
 +                    hours.addItem((i < 10) ? "0" + i : "" + i);
 +                }
 +            }
 +
 +            hours.addChangeHandler(this);
 +            if (getDateTimeService().isTwelveHourClock()) {
 +                ampm = createListBox();
 +                final String[] ampmText = getDateTimeService().getAmPmStrings();
 +                ampm.addItem(ampmText[0]);
 +                ampm.addItem(ampmText[1]);
 +                ampm.addChangeHandler(this);
 +            }
 +
 +            if (getResolution() >= VDateField.RESOLUTION_MIN) {
 +                mins = createListBox();
 +                for (int i = 0; i < 60; i++) {
 +                    mins.addItem((i < 10) ? "0" + i : "" + i);
 +                }
 +                mins.addChangeHandler(this);
 +            }
 +            if (getResolution() >= VDateField.RESOLUTION_SEC) {
 +                sec = createListBox();
 +                for (int i = 0; i < 60; i++) {
 +                    sec.addItem((i < 10) ? "0" + i : "" + i);
 +                }
 +                sec.addChangeHandler(this);
 +            }
 +
 +            final String delimiter = getDateTimeService().getClockDelimeter();
 +            if (isReadonly()) {
 +                int h = 0;
 +                if (value != null) {
 +                    h = value.getHours();
 +                }
 +                if (getDateTimeService().isTwelveHourClock()) {
 +                    h -= h < 12 ? 0 : 12;
 +                }
 +                add(new VLabel(h < 10 ? "0" + h : "" + h));
 +            } else {
 +                add(hours);
 +            }
 +
 +            if (getResolution() >= VDateField.RESOLUTION_MIN) {
 +                add(new VLabel(delimiter));
 +                if (isReadonly()) {
 +                    final int m = mins.getSelectedIndex();
 +                    add(new VLabel(m < 10 ? "0" + m : "" + m));
 +                } else {
 +                    add(mins);
 +                }
 +            }
 +            if (getResolution() >= VDateField.RESOLUTION_SEC) {
 +                add(new VLabel(delimiter));
 +                if (isReadonly()) {
 +                    final int s = sec.getSelectedIndex();
 +                    add(new VLabel(s < 10 ? "0" + s : "" + s));
 +                } else {
 +                    add(sec);
 +                }
 +            }
 +            if (getResolution() == VDateField.RESOLUTION_HOUR) {
 +                add(new VLabel(delimiter + "00")); // o'clock
 +            }
 +            if (getDateTimeService().isTwelveHourClock()) {
 +                add(new VLabel("&nbsp;"));
 +                if (isReadonly()) {
 +                    int i = 0;
 +                    if (value != null) {
 +                        i = (value.getHours() < 12) ? 0 : 1;
 +                    }
 +                    add(new VLabel(ampm.getItemText(i)));
 +                } else {
 +                    add(ampm);
 +                }
 +            }
 +
 +            if (isReadonly()) {
 +                return;
 +            }
 +
 +            // Update times
 +            updateTimes();
 +
 +            ListBox lastDropDown = getLastDropDown();
 +            lastDropDown.addKeyDownHandler(new KeyDownHandler() {
 +                @Override
 +                public void onKeyDown(KeyDownEvent event) {
 +                    boolean shiftKey = event.getNativeEvent().getShiftKey();
 +                    if (shiftKey) {
 +                        return;
 +                    } else {
 +                        int nativeKeyCode = event.getNativeKeyCode();
 +                        if (nativeKeyCode == KeyCodes.KEY_TAB) {
 +                            onTabOut(event);
 +                        }
 +                    }
 +                }
 +            });
 +
 +        }
 +
 +        private ListBox getLastDropDown() {
 +            int i = getWidgetCount() - 1;
 +            while (i >= 0) {
 +                Widget widget = getWidget(i);
 +                if (widget instanceof ListBox) {
 +                    return (ListBox) widget;
 +                }
 +                i--;
 +            }
 +            return null;
 +        }
 +
 +        /**
 +         * Updates the valus to correspond to the values in value
 +         */
 +        public void updateTimes() {
 +            boolean selected = true;
 +            if (value == null) {
 +                value = new Date();
 +                selected = false;
 +            }
 +            if (getDateTimeService().isTwelveHourClock()) {
 +                int h = value.getHours();
 +                ampm.setSelectedIndex(h < 12 ? 0 : 1);
 +                h -= ampm.getSelectedIndex() * 12;
 +                hours.setSelectedIndex(h);
 +            } else {
 +                hours.setSelectedIndex(value.getHours());
 +            }
 +            if (getResolution() >= VDateField.RESOLUTION_MIN) {
 +                mins.setSelectedIndex(value.getMinutes());
 +            }
 +            if (getResolution() >= VDateField.RESOLUTION_SEC) {
 +                sec.setSelectedIndex(value.getSeconds());
 +            }
 +            if (getDateTimeService().isTwelveHourClock()) {
 +                ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1);
 +            }
 +
 +            hours.setEnabled(isEnabled());
 +            if (mins != null) {
 +                mins.setEnabled(isEnabled());
 +            }
 +            if (sec != null) {
 +                sec.setEnabled(isEnabled());
 +            }
 +            if (ampm != null) {
 +                ampm.setEnabled(isEnabled());
 +            }
 +
 +        }
 +
 +        private int getMilliseconds() {
 +            return DateTimeService.getMilliseconds(value);
 +        }
 +
 +        private DateTimeService getDateTimeService() {
 +            if (dateTimeService == null) {
 +                dateTimeService = new DateTimeService();
 +            }
 +            return dateTimeService;
 +        }
 +
 +        /*
 +         * (non-Javadoc) VT
 +         * 
 +         * @see
 +         * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
 +         * .event.dom.client.ChangeEvent)
 +         */
 +        @Override
 +        public void onChange(ChangeEvent event) {
 +            /*
 +             * Value from dropdowns gets always set for the value. Like year and
 +             * month when resolution is month or year.
 +             */
 +            if (event.getSource() == hours) {
 +                int h = hours.getSelectedIndex();
 +                if (getDateTimeService().isTwelveHourClock()) {
 +                    h = h + ampm.getSelectedIndex() * 12;
 +                }
 +                value.setHours(h);
 +                if (timeChangeListener != null) {
 +                    timeChangeListener.changed(h, value.getMinutes(),
 +                            value.getSeconds(),
 +                            DateTimeService.getMilliseconds(value));
 +                }
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } else if (event.getSource() == mins) {
 +                final int m = mins.getSelectedIndex();
 +                value.setMinutes(m);
 +                if (timeChangeListener != null) {
 +                    timeChangeListener.changed(value.getHours(), m,
 +                            value.getSeconds(),
 +                            DateTimeService.getMilliseconds(value));
 +                }
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } else if (event.getSource() == sec) {
 +                final int s = sec.getSelectedIndex();
 +                value.setSeconds(s);
 +                if (timeChangeListener != null) {
 +                    timeChangeListener.changed(value.getHours(),
 +                            value.getMinutes(), s,
 +                            DateTimeService.getMilliseconds(value));
 +                }
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } else if (event.getSource() == ampm) {
 +                final int h = hours.getSelectedIndex()
 +                        + (ampm.getSelectedIndex() * 12);
 +                value.setHours(h);
 +                if (timeChangeListener != null) {
 +                    timeChangeListener.changed(h, value.getMinutes(),
 +                            value.getSeconds(),
 +                            DateTimeService.getMilliseconds(value));
 +                }
 +                event.preventDefault();
 +                event.stopPropagation();
 +            }
 +        }
 +
 +    }
 +
 +    /**
 +     * A widget representing a single day in the calendar panel.
 +     */
 +    private class Day extends InlineHTML {
 +        private static final String BASECLASS = VDateField.CLASSNAME
 +                + "-calendarpanel-day";
 +        private final Date date;
 +
 +        Day(Date date) {
 +            super("" + date.getDate());
 +            setStyleName(BASECLASS);
 +            this.date = date;
 +            addClickHandler(dayClickHandler);
 +        }
 +
 +        public Date getDate() {
 +            return date;
 +        }
 +    }
 +
 +    public Date getDate() {
 +        return value;
 +    }
 +
 +    /**
 +     * If true should be returned if the panel will not be used after this
 +     * event.
 +     * 
 +     * @param event
 +     * @return
 +     */
 +    protected boolean onTabOut(DomEvent<?> event) {
 +        if (focusOutListener != null) {
 +            return focusOutListener.onFocusOut(event);
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * A focus out listener is triggered when the panel loosed focus. This can
 +     * happen either after a user clicks outside the panel or tabs out.
 +     * 
 +     * @param listener
 +     *            The listener to trigger
 +     */
 +    public void setFocusOutListener(FocusOutListener listener) {
 +        focusOutListener = listener;
 +    }
 +
 +    /**
 +     * The submit listener is called when the user selects a value from the
 +     * calender either by clicking the day or selects it by keyboard.
 +     * 
 +     * @param submitListener
 +     *            The listener to trigger
 +     */
 +    public void setSubmitListener(SubmitListener submitListener) {
 +        this.submitListener = submitListener;
 +    }
 +
 +    /**
 +     * The given FocusChangeListener is notified when the focused date changes
 +     * by user either clicking on a new date or by using the keyboard.
 +     * 
 +     * @param listener
 +     *            The FocusChangeListener to be notified
 +     */
 +    public void setFocusChangeListener(FocusChangeListener listener) {
 +        focusChangeListener = listener;
 +    }
 +
 +    /**
 +     * The time change listener is triggered when the user changes the time.
 +     * 
 +     * @param listener
 +     */
 +    public void setTimeChangeListener(TimeChangeListener listener) {
 +        timeChangeListener = listener;
 +    }
 +
 +    /**
 +     * Returns the submit listener that listens to selection made from the panel
 +     * 
 +     * @return The listener or NULL if no listener has been set
 +     */
 +    public SubmitListener getSubmitListener() {
 +        return submitListener;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
 +     * .dom.client.BlurEvent)
 +     */
 +    @Override
 +    public void onBlur(final BlurEvent event) {
 +        if (event.getSource() instanceof VCalendarPanel) {
 +            hasFocus = false;
 +            focusDay(null);
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
 +     * .dom.client.FocusEvent)
 +     */
 +    @Override
 +    public void onFocus(FocusEvent event) {
 +        if (event.getSource() instanceof VCalendarPanel) {
 +            hasFocus = true;
 +
 +            // Focuses the current day if the calendar shows the days
 +            if (focusedDay != null) {
 +                focusDay(focusedDate);
 +            }
 +        }
 +    }
 +
 +    private static final String SUBPART_NEXT_MONTH = "nextmon";
 +    private static final String SUBPART_PREV_MONTH = "prevmon";
 +
 +    private static final String SUBPART_NEXT_YEAR = "nexty";
 +    private static final String SUBPART_PREV_YEAR = "prevy";
 +    private static final String SUBPART_HOUR_SELECT = "h";
 +    private static final String SUBPART_MINUTE_SELECT = "m";
 +    private static final String SUBPART_SECS_SELECT = "s";
 +    private static final String SUBPART_MSECS_SELECT = "ms";
 +    private static final String SUBPART_AMPM_SELECT = "ampm";
 +    private static final String SUBPART_DAY = "day";
 +    private static final String SUBPART_MONTH_YEAR_HEADER = "header";
 +
 +    @Override
 +    public String getSubPartName(Element subElement) {
 +        if (contains(nextMonth, subElement)) {
 +            return SUBPART_NEXT_MONTH;
 +        } else if (contains(prevMonth, subElement)) {
 +            return SUBPART_PREV_MONTH;
 +        } else if (contains(nextYear, subElement)) {
 +            return SUBPART_NEXT_YEAR;
 +        } else if (contains(prevYear, subElement)) {
 +            return SUBPART_PREV_YEAR;
 +        } else if (contains(days, subElement)) {
 +            // Day, find out which dayOfMonth and use that as the identifier
 +            Day day = Util.findWidget(subElement, Day.class);
 +            if (day != null) {
 +                Date date = day.getDate();
 +                int id = date.getDate();
 +                // Zero or negative ids map to days of the preceding month,
 +                // past-the-end-of-month ids to days of the following month
 +                if (date.getMonth() < displayedMonth.getMonth()) {
 +                    id -= DateTimeService.getNumberOfDaysInMonth(date);
 +                } else if (date.getMonth() > displayedMonth.getMonth()) {
 +                    id += DateTimeService
 +                            .getNumberOfDaysInMonth(displayedMonth);
 +                }
 +                return SUBPART_DAY + id;
 +            }
 +        } else if (time != null) {
 +            if (contains(time.hours, subElement)) {
 +                return SUBPART_HOUR_SELECT;
 +            } else if (contains(time.mins, subElement)) {
 +                return SUBPART_MINUTE_SELECT;
 +            } else if (contains(time.sec, subElement)) {
 +                return SUBPART_SECS_SELECT;
 +            } else if (contains(time.ampm, subElement)) {
 +                return SUBPART_AMPM_SELECT;
 +
 +            }
 +        } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) {
 +            return SUBPART_MONTH_YEAR_HEADER;
 +        }
 +
 +        return null;
 +    }
 +
 +    /**
 +     * Checks if subElement is inside the widget DOM hierarchy.
 +     * 
 +     * @param w
 +     * @param subElement
 +     * @return true if {@code w} is a parent of subElement, false otherwise.
 +     */
 +    private boolean contains(Widget w, Element subElement) {
 +        if (w == null || w.getElement() == null) {
 +            return false;
 +        }
 +
 +        return w.getElement().isOrHasChild(subElement);
 +    }
 +
 +    @Override
 +    public Element getSubPartElement(String subPart) {
 +        if (SUBPART_NEXT_MONTH.equals(subPart)) {
 +            return nextMonth.getElement();
 +        }
 +        if (SUBPART_PREV_MONTH.equals(subPart)) {
 +            return prevMonth.getElement();
 +        }
 +        if (SUBPART_NEXT_YEAR.equals(subPart)) {
 +            return nextYear.getElement();
 +        }
 +        if (SUBPART_PREV_YEAR.equals(subPart)) {
 +            return prevYear.getElement();
 +        }
 +        if (SUBPART_HOUR_SELECT.equals(subPart)) {
 +            return time.hours.getElement();
 +        }
 +        if (SUBPART_MINUTE_SELECT.equals(subPart)) {
 +            return time.mins.getElement();
 +        }
 +        if (SUBPART_SECS_SELECT.equals(subPart)) {
 +            return time.sec.getElement();
 +        }
 +        if (SUBPART_AMPM_SELECT.equals(subPart)) {
 +            return time.ampm.getElement();
 +        }
 +        if (subPart.startsWith(SUBPART_DAY)) {
 +            // Zero or negative ids map to days in the preceding month,
 +            // past-the-end-of-month ids to days in the following month
 +            int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY
 +                    .length()));
 +            Date date = new Date(displayedMonth.getYear(),
 +                    displayedMonth.getMonth(), dayOfMonth);
 +            Iterator<Widget> iter = days.iterator();
 +            while (iter.hasNext()) {
 +                Widget w = iter.next();
 +                if (w instanceof Day) {
 +                    Day day = (Day) w;
 +                    if (day.getDate().equals(date)) {
 +                        return day.getElement();
 +                    }
 +                }
 +            }
 +        }
 +
 +        if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
 +            return (Element) getCellFormatter().getElement(0, 2).getChild(0);
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    protected void onDetach() {
 +        super.onDetach();
 +        if (mouseTimer != null) {
 +            mouseTimer.cancel();
 +        }
 +    }
 +}
index f673babc26920ecc387b954bf16caffc26afd4bb,0000000000000000000000000000000000000000..442fa095d49fbdd2f071892bc0c0eb5defeffc2b
mode 100644,000000..100644
--- /dev/null
@@@ -1,1614 -1,0 +1,1618 @@@
-         if (dataSource != null) {
-             // Gets the correct value from datasource
-             T newFieldValue;
-             try {
-                 // Discards buffer by overwriting from datasource
-                 newFieldValue = convertFromDataSource(getDataSourceValue());
-                 // If successful, remove set the buffering state to be ok
-                 if (getCurrentBufferedSourceException() != null) {
-                     setCurrentBufferedSourceException(null);
-                 }
-             } catch (final Throwable e) {
-                 // FIXME: What should really be done here if conversion fails?
-                 // Sets the buffering state
-                 currentBufferedSourceException = new Buffered.SourceException(
-                         this, e);
-                 markAsDirty();
-                 // Throws the source exception
-                 throw currentBufferedSourceException;
-             }
-             final boolean wasModified = isModified();
-             setModified(false);
-             // If the new value differs from the previous one
-             if (!equals(newFieldValue, getInternalValue())) {
-                 setInternalValue(newFieldValue);
-                 fireValueChange(false);
-             } else if (wasModified) {
-                 // If the value did not change, but the modification status did
-                 markAsDirty();
-             }
-         }
 +/*
 + * Copyright 2011 Vaadin Ltd.
 + * 
 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 + * use this file except in compliance with the License. You may obtain a copy of
 + * the License at
 + * 
 + * http://www.apache.org/licenses/LICENSE-2.0
 + * 
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 + * License for the specific language governing permissions and limitations under
 + * the License.
 + */
 +
 +package com.vaadin.ui;
 +
 +import java.io.Serializable;
 +import java.lang.reflect.Method;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.Iterator;
 +import java.util.LinkedList;
 +import java.util.List;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.data.Buffered;
 +import com.vaadin.data.Property;
 +import com.vaadin.data.Validatable;
 +import com.vaadin.data.Validator;
 +import com.vaadin.data.Validator.InvalidValueException;
 +import com.vaadin.data.util.converter.Converter;
 +import com.vaadin.data.util.converter.Converter.ConversionException;
 +import com.vaadin.data.util.converter.ConverterUtil;
 +import com.vaadin.event.Action;
 +import com.vaadin.event.ShortcutAction;
 +import com.vaadin.event.ShortcutListener;
 +import com.vaadin.server.AbstractErrorMessage;
 +import com.vaadin.server.CompositeErrorMessage;
 +import com.vaadin.server.ErrorMessage;
 +import com.vaadin.shared.AbstractFieldState;
 +
 +/**
 + * <p>
 + * Abstract field component for implementing buffered property editors. The
 + * field may hold an internal value, or it may be connected to any data source
 + * that implements the {@link com.vaadin.data.Property}interface.
 + * <code>AbstractField</code> implements that interface itself, too, so
 + * accessing the Property value represented by it is straightforward.
 + * </p>
 + * 
 + * <p>
 + * AbstractField also provides the {@link com.vaadin.data.Buffered} interface
 + * for buffering the data source value. By default the Field is in write
 + * through-mode and {@link #setWriteThrough(boolean)}should be called to enable
 + * buffering.
 + * </p>
 + * 
 + * <p>
 + * The class also supports {@link com.vaadin.data.Validator validators} to make
 + * sure the value contained in the field is valid.
 + * </p>
 + * 
 + * @author Vaadin Ltd.
 + * @since 3.0
 + */
 +@SuppressWarnings("serial")
 +public abstract class AbstractField<T> extends AbstractComponent implements
 +        Field<T>, Property.ReadOnlyStatusChangeListener,
 +        Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier {
 +
 +    /* Private members */
 +
 +    private static final Logger logger = Logger.getLogger(AbstractField.class
 +            .getName());
 +
 +    /**
 +     * Value of the abstract field.
 +     */
 +    private T value;
 +
 +    /**
 +     * A converter used to convert from the data model type to the field type
 +     * and vice versa.
 +     */
 +    private Converter<T, Object> converter = null;
 +    /**
 +     * Connected data-source.
 +     */
 +    private Property<?> dataSource = null;
 +
 +    /**
 +     * The list of validators.
 +     */
 +    private LinkedList<Validator> validators = null;
 +
 +    /**
 +     * True if field is in buffered mode, false otherwise
 +     */
 +    private boolean buffered;
 +
 +    /**
 +     * Flag to indicate that the field is currently committing its value to the
 +     * datasource.
 +     */
 +    private boolean committingValueToDataSource = false;
 +
 +    /**
 +     * Current source exception.
 +     */
 +    private Buffered.SourceException currentBufferedSourceException = null;
 +
 +    /**
 +     * Are the invalid values allowed in fields ?
 +     */
 +    private boolean invalidAllowed = true;
 +
 +    /**
 +     * Are the invalid values committed ?
 +     */
 +    private boolean invalidCommitted = false;
 +
 +    /**
 +     * The error message for the exception that is thrown when the field is
 +     * required but empty.
 +     */
 +    private String requiredError = "";
 +
 +    /**
 +     * The error message that is shown when the field value cannot be converted.
 +     */
 +    private String conversionError = "Could not convert value to {0}";
 +
 +    /**
 +     * Is automatic validation enabled.
 +     */
 +    private boolean validationVisible = true;
 +
 +    private boolean valueWasModifiedByDataSourceDuringCommit;
 +
 +    /**
 +     * Whether this field is currently registered as listening to events from
 +     * its data source.
 +     * 
 +     * @see #setPropertyDataSource(Property)
 +     * @see #addPropertyListeners()
 +     * @see #removePropertyListeners()
 +     */
 +    private boolean isListeningToPropertyEvents = false;
 +
 +    /* Component basics */
 +
 +    /*
 +     * Paints the field. Don't add a JavaDoc comment here, we use the default
 +     * documentation from the implemented interface.
 +     */
 +
 +    /**
 +     * Returns true if the error indicator be hidden when painting the component
 +     * even when there are errors.
 +     * 
 +     * This is a mostly internal method, but can be overridden in subclasses
 +     * e.g. if the error indicator should also be shown for empty fields in some
 +     * cases.
 +     * 
 +     * @return true to hide the error indicator, false to use the normal logic
 +     *         to show it when there are errors
 +     */
 +    protected boolean shouldHideErrors() {
 +        // getErrorMessage() can still return something else than null based on
 +        // validation etc.
 +        return isRequired() && isEmpty() && getComponentError() == null;
 +    }
 +
 +    /**
 +     * Returns the type of the Field. The methods <code>getValue</code> and
 +     * <code>setValue</code> must be compatible with this type: one must be able
 +     * to safely cast the value returned from <code>getValue</code> to the given
 +     * type and pass any variable assignable to this type as an argument to
 +     * <code>setValue</code>.
 +     * 
 +     * @return the type of the Field
 +     */
 +    @Override
 +    public abstract Class<? extends T> getType();
 +
 +    /**
 +     * The abstract field is read only also if the data source is in read only
 +     * mode.
 +     */
 +    @Override
 +    public boolean isReadOnly() {
 +        return super.isReadOnly()
 +                || (dataSource != null && dataSource.isReadOnly());
 +    }
 +
 +    /**
 +     * Changes the readonly state and throw read-only status change events.
 +     * 
 +     * @see com.vaadin.ui.Component#setReadOnly(boolean)
 +     */
 +    @Override
 +    public void setReadOnly(boolean readOnly) {
 +        super.setReadOnly(readOnly);
 +        fireReadOnlyStatusChange();
 +    }
 +
 +    /**
 +     * Tests if the invalid data is committed to datasource.
 +     * 
 +     * @see com.vaadin.data.BufferedValidatable#isInvalidCommitted()
 +     */
 +    @Override
 +    public boolean isInvalidCommitted() {
 +        return invalidCommitted;
 +    }
 +
 +    /**
 +     * Sets if the invalid data should be committed to datasource.
 +     * 
 +     * @see com.vaadin.data.BufferedValidatable#setInvalidCommitted(boolean)
 +     */
 +    @Override
 +    public void setInvalidCommitted(boolean isCommitted) {
 +        invalidCommitted = isCommitted;
 +    }
 +
 +    /*
 +     * Saves the current value to the data source Don't add a JavaDoc comment
 +     * here, we use the default documentation from the implemented interface.
 +     */
 +    @Override
 +    public void commit() throws Buffered.SourceException, InvalidValueException {
 +        if (dataSource != null && !dataSource.isReadOnly()) {
 +            if ((isInvalidCommitted() || isValid())) {
 +                try {
 +
 +                    // Commits the value to datasource.
 +                    valueWasModifiedByDataSourceDuringCommit = false;
 +                    committingValueToDataSource = true;
 +                    getPropertyDataSource().setValue(getConvertedValue());
 +                } catch (final Throwable e) {
 +
 +                    // Sets the buffering state.
 +                    SourceException sourceException = new Buffered.SourceException(
 +                            this, e);
 +                    setCurrentBufferedSourceException(sourceException);
 +
 +                    // Throws the source exception.
 +                    throw sourceException;
 +                } finally {
 +                    committingValueToDataSource = false;
 +                }
 +            } else {
 +                /* An invalid value and we don't allow them, throw the exception */
 +                validate();
 +            }
 +        }
 +
 +        // The abstract field is not modified anymore
 +        if (isModified()) {
 +            setModified(false);
 +        }
 +
 +        // If successful, remove set the buffering state to be ok
 +        if (getCurrentBufferedSourceException() != null) {
 +            setCurrentBufferedSourceException(null);
 +        }
 +
 +        if (valueWasModifiedByDataSourceDuringCommit) {
 +            valueWasModifiedByDataSourceDuringCommit = false;
 +            fireValueChange(false);
 +        }
 +
 +    }
 +
 +    /*
 +     * Updates the value from the data source. Don't add a JavaDoc comment here,
 +     * we use the default documentation from the implemented interface.
 +     */
 +    @Override
 +    public void discard() throws Buffered.SourceException {
-                 discard();
++        updateValueFromDataSource();
 +    }
 +
 +    /**
 +     * Gets the value from the data source. This is only here because of clarity
 +     * in the code that handles both the data model value and the field value.
 +     * 
 +     * @return The value of the property data source
 +     */
 +    private Object getDataSourceValue() {
 +        return dataSource.getValue();
 +    }
 +
 +    /**
 +     * Returns the field value. This is always identical to {@link #getValue()}
 +     * and only here because of clarity in the code that handles both the data
 +     * model value and the field value.
 +     * 
 +     * @return The value of the field
 +     */
 +    private T getFieldValue() {
 +        // Give the value from abstract buffers if the field if possible
 +        if (dataSource == null || isBuffered() || isModified()) {
 +            return getInternalValue();
 +        }
 +
 +        // There is no buffered value so use whatever the data model provides
 +        return convertFromDataSource(getDataSourceValue());
 +    }
 +
 +    /*
 +     * Has the field been modified since the last commit()? Don't add a JavaDoc
 +     * comment here, we use the default documentation from the implemented
 +     * interface.
 +     */
 +    @Override
 +    public boolean isModified() {
 +        return getState().modified;
 +    }
 +
 +    private void setModified(boolean modified) {
 +        getState().modified = modified;
 +    }
 +
 +    /**
 +     * Sets the buffered mode of this Field.
 +     * <p>
 +     * When the field is in buffered mode, changes will not be committed to the
 +     * property data source until {@link #commit()} is called.
 +     * </p>
 +     * <p>
 +     * Setting buffered mode from true to false will commit any pending changes.
 +     * </p>
 +     * <p>
 +     * 
 +     * </p>
 +     * 
 +     * @since 7.0.0
 +     * @param buffered
 +     *            true if buffered mode should be turned on, false otherwise
 +     */
 +    @Override
 +    public void setBuffered(boolean buffered) {
 +        if (this.buffered == buffered) {
 +            return;
 +        }
 +        this.buffered = buffered;
 +        if (!buffered) {
 +            commit();
 +        }
 +    }
 +
 +    /**
 +     * Checks the buffered mode of this Field.
 +     * 
 +     * @return true if buffered mode is on, false otherwise
 +     */
 +    @Override
 +    public boolean isBuffered() {
 +        return buffered;
 +    }
 +
 +    /* Property interface implementation */
 +
 +    /**
 +     * Returns the (field) value converted to a String using toString().
 +     * 
 +     * @see java.lang.Object#toString()
 +     * @deprecated Instead use {@link #getValue()} to get the value of the
 +     *             field, {@link #getConvertedValue()} to get the field value
 +     *             converted to the data model type or
 +     *             {@link #getPropertyDataSource()} .getValue() to get the value
 +     *             of the data source.
 +     */
 +    @Deprecated
 +    @Override
 +    public String toString() {
 +        logger.warning("You are using AbstractField.toString() to get the value for a "
 +                + getClass().getSimpleName()
 +                + ". This is not recommended and will not be supported in future versions.");
 +        final Object value = getFieldValue();
 +        if (value == null) {
 +            return null;
 +        }
 +        return value.toString();
 +    }
 +
 +    /**
 +     * Gets the current value of the field.
 +     * 
 +     * <p>
 +     * This is the visible, modified and possible invalid value the user have
 +     * entered to the field.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note that the object returned is compatible with getType(). For example,
 +     * if the type is String, this returns Strings even when the underlying
 +     * datasource is of some other type. In order to access the converted value,
 +     * use {@link #getConvertedValue()} and to access the value of the property
 +     * data source, use {@link Property#getValue()} for the property data
 +     * source.
 +     * </p>
 +     * 
 +     * <p>
 +     * Since Vaadin 7.0, no implicit conversions between other data types and
 +     * String are performed, but a converter is used if set.
 +     * </p>
 +     * 
 +     * @return the current value of the field.
 +     */
 +    @Override
 +    public T getValue() {
 +        return getFieldValue();
 +    }
 +
 +    /**
 +     * Sets the value of the field.
 +     * 
 +     * @param newFieldValue
 +     *            the New value of the field.
 +     * @throws Property.ReadOnlyException
 +     */
 +    @Override
 +    public void setValue(Object newFieldValue)
 +            throws Property.ReadOnlyException, Converter.ConversionException {
 +        // This check is needed as long as setValue accepts Object instead of T
 +        if (newFieldValue != null) {
 +            if (!getType().isAssignableFrom(newFieldValue.getClass())) {
 +                throw new Converter.ConversionException("Value of type "
 +                        + newFieldValue.getClass() + " cannot be assigned to "
 +                        + getType().getName());
 +            }
 +        }
 +        setValue((T) newFieldValue, false);
 +    }
 +
 +    /**
 +     * Sets the value of the field.
 +     * 
 +     * @param newFieldValue
 +     *            the New value of the field.
 +     * @param repaintIsNotNeeded
 +     *            True iff caller is sure that repaint is not needed.
 +     * @throws Property.ReadOnlyException
 +     */
 +    protected void setValue(T newFieldValue, boolean repaintIsNotNeeded)
 +            throws Property.ReadOnlyException, Converter.ConversionException,
 +            InvalidValueException {
 +
 +        if (!equals(newFieldValue, getInternalValue())) {
 +
 +            // Read only fields can not be changed
 +            if (isReadOnly()) {
 +                throw new Property.ReadOnlyException();
 +            }
 +
 +            // Repaint is needed even when the client thinks that it knows the
 +            // new state if validity of the component may change
 +            if (repaintIsNotNeeded
 +                    && (isRequired() || getValidators() != null || getConverter() != null)) {
 +                repaintIsNotNeeded = false;
 +            }
 +
 +            if (!isInvalidAllowed()) {
 +                /*
 +                 * If invalid values are not allowed the value must be validated
 +                 * before it is set. If validation fails, the
 +                 * InvalidValueException is thrown and the internal value is not
 +                 * updated.
 +                 */
 +                validate(newFieldValue);
 +            }
 +
 +            // Changes the value
 +            setInternalValue(newFieldValue);
 +            setModified(dataSource != null);
 +
 +            valueWasModifiedByDataSourceDuringCommit = false;
 +            // In not buffering, try to commit
 +            if (!isBuffered() && dataSource != null
 +                    && (isInvalidCommitted() || isValid())) {
 +                try {
 +
 +                    // Commits the value to datasource
 +                    committingValueToDataSource = true;
 +                    getPropertyDataSource().setValue(
 +                            convertToModel(newFieldValue));
 +
 +                    // The buffer is now unmodified
 +                    setModified(false);
 +
 +                } catch (final Throwable e) {
 +
 +                    // Sets the buffering state
 +                    currentBufferedSourceException = new Buffered.SourceException(
 +                            this, e);
 +                    markAsDirty();
 +
 +                    // Throws the source exception
 +                    throw currentBufferedSourceException;
 +                } finally {
 +                    committingValueToDataSource = false;
 +                }
 +            }
 +
 +            // If successful, remove set the buffering state to be ok
 +            if (getCurrentBufferedSourceException() != null) {
 +                setCurrentBufferedSourceException(null);
 +            }
 +
 +            if (valueWasModifiedByDataSourceDuringCommit) {
 +                /*
 +                 * Value was modified by datasource. Force repaint even if
 +                 * repaint was not requested.
 +                 */
 +                valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false;
 +            }
 +
 +            // Fires the value change
 +            fireValueChange(repaintIsNotNeeded);
 +
 +        }
 +    }
 +
 +    private static boolean equals(Object value1, Object value2) {
 +        if (value1 == null) {
 +            return value2 == null;
 +        }
 +        return value1.equals(value2);
 +    }
 +
 +    /* External data source */
 +
 +    /**
 +     * Gets the current data source of the field, if any.
 +     * 
 +     * @return the current data source as a Property, or <code>null</code> if
 +     *         none defined.
 +     */
 +    @Override
 +    public Property getPropertyDataSource() {
 +        return dataSource;
 +    }
 +
 +    /**
 +     * <p>
 +     * Sets the specified Property as the data source for the field. All
 +     * uncommitted changes are replaced with a value from the new data source.
 +     * </p>
 +     * 
 +     * <p>
 +     * If the datasource has any validators, the same validators are added to
 +     * the field. Because the default behavior of the field is to allow invalid
 +     * values, but not to allow committing them, this only adds visual error
 +     * messages to fields and do not allow committing them as long as the value
 +     * is invalid. After the value is valid, the error message is not shown and
 +     * the commit can be done normally.
 +     * </p>
 +     * 
 +     * <p>
 +     * If the data source implements
 +     * {@link com.vaadin.data.Property.ValueChangeNotifier} and/or
 +     * {@link com.vaadin.data.Property.ReadOnlyStatusChangeNotifier}, the field
 +     * registers itself as a listener and updates itself according to the events
 +     * it receives. To avoid memory leaks caused by references to a field no
 +     * longer in use, the listener registrations are removed on
 +     * {@link AbstractField#detach() detach} and re-added on
 +     * {@link AbstractField#attach() attach}.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note: before 6.5 we actually called discard() method in the beginning of
 +     * the method. This was removed to simplify implementation, avoid excess
 +     * calls to backing property and to avoid odd value change events that were
 +     * previously fired (developer expects 0-1 value change events if this
 +     * method is called). Some complex field implementations might now need to
 +     * override this method to do housekeeping similar to discard().
 +     * </p>
 +     * 
 +     * @param newDataSource
 +     *            the new data source Property.
 +     */
 +    @Override
 +    public void setPropertyDataSource(Property newDataSource) {
 +
 +        // Saves the old value
 +        final Object oldValue = getInternalValue();
 +
 +        // Stop listening to the old data source
 +        removePropertyListeners();
 +
 +        // Sets the new data source
 +        dataSource = newDataSource;
 +        getState().propertyReadOnly = dataSource == null ? false : dataSource
 +                .isReadOnly();
 +
 +        // Check if the current converter is compatible.
 +        if (newDataSource != null
 +                && !ConverterUtil.canConverterHandle(getConverter(), getType(),
 +                        newDataSource.getType())) {
 +            // Changing from e.g. Number -> Double should set a new converter,
 +            // changing from Double -> Number can keep the old one (Property
 +            // accepts Number)
 +
 +            // Set a new converter if there is a new data source and
 +            // there is no old converter or the old is incompatible.
 +            setConverter(newDataSource.getType());
 +        }
 +        // Gets the value from source
 +        try {
 +            if (dataSource != null) {
 +                T fieldValue = convertFromDataSource(getDataSourceValue());
 +                setInternalValue(fieldValue);
 +            }
 +            setModified(false);
 +            if (getCurrentBufferedSourceException() != null) {
 +                setCurrentBufferedSourceException(null);
 +            }
 +        } catch (final Throwable e) {
 +            setCurrentBufferedSourceException(new Buffered.SourceException(
 +                    this, e));
 +            setModified(true);
 +        }
 +
 +        // Listen to new data source if possible
 +        addPropertyListeners();
 +
 +        // Copy the validators from the data source
 +        if (dataSource instanceof Validatable) {
 +            final Collection<Validator> validators = ((Validatable) dataSource)
 +                    .getValidators();
 +            if (validators != null) {
 +                for (final Iterator<Validator> i = validators.iterator(); i
 +                        .hasNext();) {
 +                    addValidator(i.next());
 +                }
 +            }
 +        }
 +
 +        // Fires value change if the value has changed
 +        T value = getInternalValue();
 +        if ((value != oldValue)
 +                && ((value != null && !value.equals(oldValue)) || value == null)) {
 +            fireValueChange(false);
 +        }
 +    }
 +
 +    /**
 +     * Retrieves a converter for the field from the converter factory defined
 +     * for the application. Clears the converter if no application reference is
 +     * available or if the factory returns null.
 +     * 
 +     * @param datamodelType
 +     *            The type of the data model that we want to be able to convert
 +     *            from
 +     */
 +    public void setConverter(Class<?> datamodelType) {
 +        Converter<T, ?> c = (Converter<T, ?>) ConverterUtil.getConverter(
 +                getType(), datamodelType, getSession());
 +        setConverter(c);
 +    }
 +
 +    /**
 +     * Convert the given value from the data source type to the UI type.
 +     * 
 +     * @param newValue
 +     *            The data source value to convert.
 +     * @return The converted value that is compatible with the UI type or the
 +     *         original value if its type is compatible and no converter is set.
 +     * @throws Converter.ConversionException
 +     *             if there is no converter and the type is not compatible with
 +     *             the data source type.
 +     */
 +    private T convertFromDataSource(Object newValue) {
 +        return ConverterUtil.convertFromModel(newValue, getType(),
 +                getConverter(), getLocale());
 +    }
 +
 +    /**
 +     * Convert the given value from the UI type to the data source type.
 +     * 
 +     * @param fieldValue
 +     *            The value to convert. Typically returned by
 +     *            {@link #getFieldValue()}
 +     * @return The converted value that is compatible with the data source type.
 +     * @throws Converter.ConversionException
 +     *             if there is no converter and the type is not compatible with
 +     *             the data source type.
 +     */
 +    private Object convertToModel(T fieldValue)
 +            throws Converter.ConversionException {
 +        try {
 +            Class<?> modelType = null;
 +            Property pd = getPropertyDataSource();
 +            if (pd != null) {
 +                modelType = pd.getType();
 +            } else if (getConverter() != null) {
 +                modelType = getConverter().getModelType();
 +            }
 +            return ConverterUtil.convertToModel(fieldValue,
 +                    (Class<Object>) modelType, getConverter(), getLocale());
 +        } catch (ConversionException e) {
 +            throw new ConversionException(
 +                    getConversionError(converter.getModelType()), e);
 +        }
 +    }
 +
 +    /**
 +     * Returns the conversion error with {0} replaced by the data source type.
 +     * 
 +     * @param dataSourceType
 +     *            The type of the data source
 +     * @return The value conversion error string with parameters replaced.
 +     */
 +    protected String getConversionError(Class<?> dataSourceType) {
 +        if (dataSourceType == null) {
 +            return getConversionError();
 +        } else {
 +            return getConversionError().replace("{0}",
 +                    dataSourceType.getSimpleName());
 +        }
 +    }
 +
 +    /**
 +     * Returns the current value (as returned by {@link #getValue()}) converted
 +     * to the data source type.
 +     * <p>
 +     * This returns the same as {@link AbstractField#getValue()} if no converter
 +     * has been set. The value is not necessarily the same as the data source
 +     * value e.g. if the field is in buffered mode and has been modified.
 +     * </p>
 +     * 
 +     * @return The converted value that is compatible with the data source type
 +     */
 +    public Object getConvertedValue() {
 +        return convertToModel(getFieldValue());
 +    }
 +
 +    /**
 +     * Sets the value of the field using a value of the data source type. The
 +     * value given is converted to the field type and then assigned to the
 +     * field. This will update the property data source in the same way as when
 +     * {@link #setValue(Object)} is called.
 +     * 
 +     * @param value
 +     *            The value to set. Must be the same type as the data source.
 +     */
 +    public void setConvertedValue(Object value) {
 +        setValue(convertFromDataSource(value));
 +    }
 +
 +    /* Validation */
 +
 +    /**
 +     * Adds a new validator for the field's value. All validators added to a
 +     * field are checked each time the its value changes.
 +     * 
 +     * @param validator
 +     *            the new validator to be added.
 +     */
 +    @Override
 +    public void addValidator(Validator validator) {
 +        if (validators == null) {
 +            validators = new LinkedList<Validator>();
 +        }
 +        validators.add(validator);
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Gets the validators of the field.
 +     * 
 +     * @return the Unmodifiable collection that holds all validators for the
 +     *         field.
 +     */
 +    @Override
 +    public Collection<Validator> getValidators() {
 +        if (validators == null || validators.isEmpty()) {
 +            return null;
 +        }
 +        return Collections.unmodifiableCollection(validators);
 +    }
 +
 +    /**
 +     * Removes the validator from the field.
 +     * 
 +     * @param validator
 +     *            the validator to remove.
 +     */
 +    @Override
 +    public void removeValidator(Validator validator) {
 +        if (validators != null) {
 +            validators.remove(validator);
 +        }
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Removes all validators from the field.
 +     */
 +    public void removeAllValidators() {
 +        if (validators != null) {
 +            validators.clear();
 +        }
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Tests the current value against registered validators if the field is not
 +     * empty. If the field is empty it is considered valid if it is not required
 +     * and invalid otherwise. Validators are never checked for empty fields.
 +     * 
 +     * In most cases, {@link #validate()} should be used instead of
 +     * {@link #isValid()} to also get the error message.
 +     * 
 +     * @return <code>true</code> if all registered validators claim that the
 +     *         current value is valid or if the field is empty and not required,
 +     *         <code>false</code> otherwise.
 +     */
 +    @Override
 +    public boolean isValid() {
 +
 +        try {
 +            validate();
 +            return true;
 +        } catch (InvalidValueException e) {
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     * Checks the validity of the Field.
 +     * 
 +     * A field is invalid if it is set as required (using
 +     * {@link #setRequired(boolean)} and is empty, if one or several of the
 +     * validators added to the field indicate it is invalid or if the value
 +     * cannot be converted provided a converter has been set.
 +     * 
 +     * The "required" validation is a built-in validation feature. If the field
 +     * is required and empty this method throws an EmptyValueException with the
 +     * error message set using {@link #setRequiredError(String)}.
 +     * 
 +     * @see com.vaadin.data.Validatable#validate()
 +     */
 +    @Override
 +    public void validate() throws Validator.InvalidValueException {
 +
 +        if (isRequired() && isEmpty()) {
 +            throw new Validator.EmptyValueException(requiredError);
 +        }
 +        validate(getFieldValue());
 +    }
 +
 +    /**
 +     * Validates that the given value pass the validators for the field.
 +     * <p>
 +     * This method does not check the requiredness of the field.
 +     * 
 +     * @param fieldValue
 +     *            The value to check
 +     * @throws Validator.InvalidValueException
 +     *             if one or several validators fail
 +     */
 +    protected void validate(T fieldValue)
 +            throws Validator.InvalidValueException {
 +
 +        Object valueToValidate = fieldValue;
 +
 +        // If there is a converter we start by converting the value as we want
 +        // to validate the converted value
 +        if (getConverter() != null) {
 +            try {
 +                valueToValidate = getConverter().convertToModel(fieldValue,
 +                        getLocale());
 +            } catch (Exception e) {
 +                throw new InvalidValueException(
 +                        getConversionError(getConverter().getModelType()));
 +            }
 +        }
 +
 +        List<InvalidValueException> validationExceptions = new ArrayList<InvalidValueException>();
 +        if (validators != null) {
 +            // Gets all the validation errors
 +            for (Validator v : validators) {
 +                try {
 +                    v.validate(valueToValidate);
 +                } catch (final Validator.InvalidValueException e) {
 +                    validationExceptions.add(e);
 +                }
 +            }
 +        }
 +
 +        // If there were no errors
 +        if (validationExceptions.isEmpty()) {
 +            return;
 +        }
 +
 +        // If only one error occurred, throw it forwards
 +        if (validationExceptions.size() == 1) {
 +            throw validationExceptions.get(0);
 +        }
 +
 +        InvalidValueException[] exceptionArray = validationExceptions
 +                .toArray(new InvalidValueException[validationExceptions.size()]);
 +
 +        // Create a composite validator and include all exceptions
 +        throw new Validator.InvalidValueException(null, exceptionArray);
 +    }
 +
 +    /**
 +     * Fields allow invalid values by default. In most cases this is wanted,
 +     * because the field otherwise visually forget the user input immediately.
 +     * 
 +     * @return true iff the invalid values are allowed.
 +     * @see com.vaadin.data.Validatable#isInvalidAllowed()
 +     */
 +    @Override
 +    public boolean isInvalidAllowed() {
 +        return invalidAllowed;
 +    }
 +
 +    /**
 +     * Fields allow invalid values by default. In most cases this is wanted,
 +     * because the field otherwise visually forget the user input immediately.
 +     * <p>
 +     * In common setting where the user wants to assure the correctness of the
 +     * datasource, but allow temporarily invalid contents in the field, the user
 +     * should add the validators to datasource, that should not allow invalid
 +     * values. The validators are automatically copied to the field when the
 +     * datasource is set.
 +     * </p>
 +     * 
 +     * @see com.vaadin.data.Validatable#setInvalidAllowed(boolean)
 +     */
 +    @Override
 +    public void setInvalidAllowed(boolean invalidAllowed)
 +            throws UnsupportedOperationException {
 +        this.invalidAllowed = invalidAllowed;
 +    }
 +
 +    /**
 +     * Error messages shown by the fields are composites of the error message
 +     * thrown by the superclasses (that is the component error message),
 +     * validation errors and buffered source errors.
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#getErrorMessage()
 +     */
 +    @Override
 +    public ErrorMessage getErrorMessage() {
 +
 +        /*
 +         * Check validation errors only if automatic validation is enabled.
 +         * Empty, required fields will generate a validation error containing
 +         * the requiredError string. For these fields the exclamation mark will
 +         * be hidden but the error must still be sent to the client.
 +         */
 +        Validator.InvalidValueException validationError = null;
 +        if (isValidationVisible()) {
 +            try {
 +                validate();
 +            } catch (Validator.InvalidValueException e) {
 +                if (!e.isInvisible()) {
 +                    validationError = e;
 +                }
 +            }
 +        }
 +
 +        // Check if there are any systems errors
 +        final ErrorMessage superError = super.getErrorMessage();
 +
 +        // Return if there are no errors at all
 +        if (superError == null && validationError == null
 +                && getCurrentBufferedSourceException() == null) {
 +            return null;
 +        }
 +
 +        // Throw combination of the error types
 +        return new CompositeErrorMessage(
 +                new ErrorMessage[] {
 +                        superError,
 +                        AbstractErrorMessage
 +                                .getErrorMessageForException(validationError),
 +                        AbstractErrorMessage
 +                                .getErrorMessageForException(getCurrentBufferedSourceException()) });
 +
 +    }
 +
 +    /* Value change events */
 +
 +    private static final Method VALUE_CHANGE_METHOD;
 +
 +    static {
 +        try {
 +            VALUE_CHANGE_METHOD = Property.ValueChangeListener.class
 +                    .getDeclaredMethod("valueChange",
 +                            new Class[] { Property.ValueChangeEvent.class });
 +        } catch (final java.lang.NoSuchMethodException e) {
 +            // This should never happen
 +            throw new java.lang.RuntimeException(
 +                    "Internal error finding methods in AbstractField");
 +        }
 +    }
 +
 +    /*
 +     * Adds a value change listener for the field. Don't add a JavaDoc comment
 +     * here, we use the default documentation from the implemented interface.
 +     */
 +    @Override
 +    public void addValueChangeListener(Property.ValueChangeListener listener) {
 +        addListener(AbstractField.ValueChangeEvent.class, listener,
 +                VALUE_CHANGE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated Since 7.0, replaced by
 +     *             {@link #addValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(Property.ValueChangeListener listener) {
 +        addValueChangeListener(listener);
 +    }
 +
 +    /*
 +     * Removes a value change listener from the field. Don't add a JavaDoc
 +     * comment here, we use the default documentation from the implemented
 +     * interface.
 +     */
 +    @Override
 +    public void removeValueChangeListener(Property.ValueChangeListener listener) {
 +        removeListener(AbstractField.ValueChangeEvent.class, listener,
 +                VALUE_CHANGE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated Since 7.0, replaced by
 +     *             {@link #removeValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(Property.ValueChangeListener listener) {
 +        removeValueChangeListener(listener);
 +    }
 +
 +    /**
 +     * Emits the value change event. The value contained in the field is
 +     * validated before the event is created.
 +     */
 +    protected void fireValueChange(boolean repaintIsNotNeeded) {
 +        fireEvent(new AbstractField.ValueChangeEvent(this));
 +        if (!repaintIsNotNeeded) {
 +            markAsDirty();
 +        }
 +    }
 +
 +    /* Read-only status change events */
 +
 +    private static final Method READ_ONLY_STATUS_CHANGE_METHOD;
 +
 +    static {
 +        try {
 +            READ_ONLY_STATUS_CHANGE_METHOD = Property.ReadOnlyStatusChangeListener.class
 +                    .getDeclaredMethod(
 +                            "readOnlyStatusChange",
 +                            new Class[] { Property.ReadOnlyStatusChangeEvent.class });
 +        } catch (final java.lang.NoSuchMethodException e) {
 +            // This should never happen
 +            throw new java.lang.RuntimeException(
 +                    "Internal error finding methods in AbstractField");
 +        }
 +    }
 +
 +    /**
 +     * React to read only status changes of the property by requesting a
 +     * repaint.
 +     * 
 +     * @see Property.ReadOnlyStatusChangeListener
 +     */
 +    @Override
 +    public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) {
 +        getState().propertyReadOnly = event.getProperty().isReadOnly();
 +    }
 +
 +    /**
 +     * An <code>Event</code> object specifying the Property whose read-only
 +     * status has changed.
 +     * 
 +     * @author Vaadin Ltd.
 +     * @since 3.0
 +     */
 +    public static class ReadOnlyStatusChangeEvent extends Component.Event
 +            implements Property.ReadOnlyStatusChangeEvent, Serializable {
 +
 +        /**
 +         * New instance of text change event.
 +         * 
 +         * @param source
 +         *            the Source of the event.
 +         */
 +        public ReadOnlyStatusChangeEvent(AbstractField source) {
 +            super(source);
 +        }
 +
 +        /**
 +         * Property where the event occurred.
 +         * 
 +         * @return the Source of the event.
 +         */
 +        @Override
 +        public Property getProperty() {
 +            return (Property) getSource();
 +        }
 +    }
 +
 +    /*
 +     * Adds a read-only status change listener for the field. Don't add a
 +     * JavaDoc comment here, we use the default documentation from the
 +     * implemented interface.
 +     */
 +    @Override
 +    public void addReadOnlyStatusChangeListener(
 +            Property.ReadOnlyStatusChangeListener listener) {
 +        addListener(Property.ReadOnlyStatusChangeEvent.class, listener,
 +                READ_ONLY_STATUS_CHANGE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated Since 7.0, replaced by
 +     *             {@link #addReadOnlyStatusChangeListener(com.vaadin.data.Property.ReadOnlyStatusChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(Property.ReadOnlyStatusChangeListener listener) {
 +        addReadOnlyStatusChangeListener(listener);
 +    }
 +
 +    /*
 +     * Removes a read-only status change listener from the field. Don't add a
 +     * JavaDoc comment here, we use the default documentation from the
 +     * implemented interface.
 +     */
 +    @Override
 +    public void removeReadOnlyStatusChangeListener(
 +            Property.ReadOnlyStatusChangeListener listener) {
 +        removeListener(Property.ReadOnlyStatusChangeEvent.class, listener,
 +                READ_ONLY_STATUS_CHANGE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated Since 7.0, replaced by
 +     *             {@link #removeReadOnlyStatusChangeListener(com.vaadin.data.Property.ReadOnlyStatusChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
 +        removeReadOnlyStatusChangeListener(listener);
 +    }
 +
 +    /**
 +     * Emits the read-only status change event. The value contained in the field
 +     * is validated before the event is created.
 +     */
 +    protected void fireReadOnlyStatusChange() {
 +        fireEvent(new AbstractField.ReadOnlyStatusChangeEvent(this));
 +    }
 +
 +    /**
 +     * This method listens to data source value changes and passes the changes
 +     * forwards.
 +     * 
 +     * Changes are not forwarded to the listeners of the field during internal
 +     * operations of the field to avoid duplicate notifications.
 +     * 
 +     * @param event
 +     *            the value change event telling the data source contents have
 +     *            changed.
 +     */
 +    @Override
 +    public void valueChange(Property.ValueChangeEvent event) {
 +        if (!isBuffered()) {
 +            if (committingValueToDataSource) {
 +                boolean propertyNotifiesOfTheBufferedValue = equals(event
 +                        .getProperty().getValue(), getInternalValue());
 +                if (!propertyNotifiesOfTheBufferedValue) {
 +                    /*
 +                     * Property (or chained property like PropertyFormatter) now
 +                     * reports different value than the one the field has just
 +                     * committed to it. In this case we respect the property
 +                     * value.
 +                     * 
 +                     * Still, we don't fire value change yet, but instead
 +                     * postpone it until "commit" is done. See setValue(Object,
 +                     * boolean) and commit().
 +                     */
 +                    readValueFromProperty(event);
 +                    valueWasModifiedByDataSourceDuringCommit = true;
 +                }
 +            } else if (!isModified()) {
 +                readValueFromProperty(event);
 +                fireValueChange(false);
 +            }
 +        }
 +    }
 +
 +    private void readValueFromProperty(Property.ValueChangeEvent event) {
 +        setInternalValue(convertFromDataSource(event.getProperty().getValue()));
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     */
 +    @Override
 +    public void focus() {
 +        super.focus();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Component.Focusable#getTabIndex()
 +     */
 +    @Override
 +    public int getTabIndex() {
 +        return getState().tabIndex;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
 +     */
 +    @Override
 +    public void setTabIndex(int tabIndex) {
 +        getState().tabIndex = tabIndex;
 +    }
 +
 +    /**
 +     * Returns the internal field value, which might not match the data source
 +     * value e.g. if the field has been modified and is not in write-through
 +     * mode.
 +     * 
 +     * This method can be overridden by subclasses together with
 +     * {@link #setInternalValue(Object)} to compute internal field value at
 +     * runtime. When doing so, typically also {@link #isModified()} needs to be
 +     * overridden and care should be taken in the management of the empty state
 +     * and buffering support.
 +     * 
 +     * @return internal field value
 +     */
 +    protected T getInternalValue() {
 +        return value;
 +    }
 +
 +    /**
 +     * Sets the internal field value. This is purely used by AbstractField to
 +     * change the internal Field value. It does not trigger valuechange events.
 +     * It can be overridden by the inheriting classes to update all dependent
 +     * variables.
 +     * 
 +     * Subclasses can also override {@link #getInternalValue()} if necessary.
 +     * 
 +     * @param newValue
 +     *            the new value to be set.
 +     */
 +    protected void setInternalValue(T newValue) {
 +        value = newValue;
 +        if (validators != null && !validators.isEmpty()) {
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Notifies the component that it is connected to an application.
 +     * 
 +     * @see com.vaadin.ui.Component#attach()
 +     */
 +    @Override
 +    public void attach() {
 +        super.attach();
 +
 +        if (!isListeningToPropertyEvents) {
 +            addPropertyListeners();
 +            if (!isModified() && !isBuffered()) {
 +                // Update value from data source
++                updateValueFromDataSource();
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public void detach() {
 +        super.detach();
 +        // Stop listening to data source events on detach to avoid a potential
 +        // memory leak. See #6155.
 +        removePropertyListeners();
 +    }
 +
 +    /**
 +     * Is this field required. Required fields must filled by the user.
 +     * 
 +     * If the field is required, it is visually indicated in the user interface.
 +     * Furthermore, setting field to be required implicitly adds "non-empty"
 +     * validator and thus isValid() == false or any isEmpty() fields. In those
 +     * cases validation errors are not painted as it is obvious that the user
 +     * must fill in the required fields.
 +     * 
 +     * On the other hand, for the non-required fields isValid() == true if the
 +     * field isEmpty() regardless of any attached validators.
 +     * 
 +     * 
 +     * @return <code>true</code> if the field is required, otherwise
 +     *         <code>false</code>.
 +     */
 +    @Override
 +    public boolean isRequired() {
 +        return getState().required;
 +    }
 +
 +    /**
 +     * Sets the field required. Required fields must filled by the user.
 +     * 
 +     * If the field is required, it is visually indicated in the user interface.
 +     * Furthermore, setting field to be required implicitly adds "non-empty"
 +     * validator and thus isValid() == false or any isEmpty() fields. In those
 +     * cases validation errors are not painted as it is obvious that the user
 +     * must fill in the required fields.
 +     * 
 +     * On the other hand, for the non-required fields isValid() == true if the
 +     * field isEmpty() regardless of any attached validators.
 +     * 
 +     * @param required
 +     *            Is the field required.
 +     */
 +    @Override
 +    public void setRequired(boolean required) {
 +        getState().required = required;
 +    }
 +
 +    /**
 +     * Set the error that is show if this field is required, but empty. When
 +     * setting requiredMessage to be "" or null, no error pop-up or exclamation
 +     * mark is shown for a empty required field. This faults to "". Even in
 +     * those cases isValid() returns false for empty required fields.
 +     * 
 +     * @param requiredMessage
 +     *            Message to be shown when this field is required, but empty.
 +     */
 +    @Override
 +    public void setRequiredError(String requiredMessage) {
 +        requiredError = requiredMessage;
 +        markAsDirty();
 +    }
 +
 +    @Override
 +    public String getRequiredError() {
 +        return requiredError;
 +    }
 +
 +    /**
 +     * Gets the error that is shown if the field value cannot be converted to
 +     * the data source type.
 +     * 
 +     * @return The error that is shown if conversion of the field value fails
 +     */
 +    public String getConversionError() {
 +        return conversionError;
 +    }
 +
 +    /**
 +     * Sets the error that is shown if the field value cannot be converted to
 +     * the data source type. If {0} is present in the message, it will be
 +     * replaced by the simple name of the data source type.
 +     * 
 +     * @param valueConversionError
 +     *            Message to be shown when conversion of the value fails
 +     */
 +    public void setConversionError(String valueConversionError) {
 +        this.conversionError = valueConversionError;
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Is the field empty?
 +     * 
 +     * In general, "empty" state is same as null. As an exception, TextField
 +     * also treats empty string as "empty".
 +     */
 +    protected boolean isEmpty() {
 +        return (getFieldValue() == null);
 +    }
 +
 +    /**
 +     * Is automatic, visible validation enabled?
 +     * 
 +     * If automatic validation is enabled, any validators connected to this
 +     * component are evaluated while painting the component and potential error
 +     * messages are sent to client. If the automatic validation is turned off,
 +     * isValid() and validate() methods still work, but one must show the
 +     * validation in their own code.
 +     * 
 +     * @return True, if automatic validation is enabled.
 +     */
 +    public boolean isValidationVisible() {
 +        return validationVisible;
 +    }
 +
 +    /**
 +     * Enable or disable automatic, visible validation.
 +     * 
 +     * If automatic validation is enabled, any validators connected to this
 +     * component are evaluated while painting the component and potential error
 +     * messages are sent to client. If the automatic validation is turned off,
 +     * isValid() and validate() methods still work, but one must show the
 +     * validation in their own code.
 +     * 
 +     * @param validateAutomatically
 +     *            True, if automatic validation is enabled.
 +     */
 +    public void setValidationVisible(boolean validateAutomatically) {
 +        if (validationVisible != validateAutomatically) {
 +            markAsDirty();
 +            validationVisible = validateAutomatically;
 +        }
 +    }
 +
 +    /**
 +     * Sets the current buffered source exception.
 +     * 
 +     * @param currentBufferedSourceException
 +     */
 +    public void setCurrentBufferedSourceException(
 +            Buffered.SourceException currentBufferedSourceException) {
 +        this.currentBufferedSourceException = currentBufferedSourceException;
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Gets the current buffered source exception.
 +     * 
 +     * @return The current source exception
 +     */
 +    protected Buffered.SourceException getCurrentBufferedSourceException() {
 +        return currentBufferedSourceException;
 +    }
 +
 +    /**
 +     * A ready-made {@link ShortcutListener} that focuses the given
 +     * {@link Focusable} (usually a {@link Field}) when the keyboard shortcut is
 +     * invoked.
 +     * 
 +     */
 +    public static class FocusShortcut extends ShortcutListener {
 +        protected Focusable focusable;
 +
 +        /**
 +         * Creates a keyboard shortcut for focusing the given {@link Focusable}
 +         * using the shorthand notation defined in {@link ShortcutAction}.
 +         * 
 +         * @param focusable
 +         *            to focused when the shortcut is invoked
 +         * @param shorthandCaption
 +         *            caption with keycode and modifiers indicated
 +         */
 +        public FocusShortcut(Focusable focusable, String shorthandCaption) {
 +            super(shorthandCaption);
 +            this.focusable = focusable;
 +        }
 +
 +        /**
 +         * Creates a keyboard shortcut for focusing the given {@link Focusable}.
 +         * 
 +         * @param focusable
 +         *            to focused when the shortcut is invoked
 +         * @param keyCode
 +         *            keycode that invokes the shortcut
 +         * @param modifiers
 +         *            modifiers required to invoke the shortcut
 +         */
 +        public FocusShortcut(Focusable focusable, int keyCode, int... modifiers) {
 +            super(null, keyCode, modifiers);
 +            this.focusable = focusable;
 +        }
 +
 +        /**
 +         * Creates a keyboard shortcut for focusing the given {@link Focusable}.
 +         * 
 +         * @param focusable
 +         *            to focused when the shortcut is invoked
 +         * @param keyCode
 +         *            keycode that invokes the shortcut
 +         */
 +        public FocusShortcut(Focusable focusable, int keyCode) {
 +            this(focusable, keyCode, null);
 +        }
 +
 +        @Override
 +        public void handleAction(Object sender, Object target) {
 +            focusable.focus();
 +        }
 +    }
 +
++    private void updateValueFromDataSource() {
++        if (dataSource != null) {
++
++            // Gets the correct value from datasource
++            T newFieldValue;
++            try {
++
++                // Discards buffer by overwriting from datasource
++                newFieldValue = convertFromDataSource(getDataSourceValue());
++
++                // If successful, remove set the buffering state to be ok
++                if (getCurrentBufferedSourceException() != null) {
++                    setCurrentBufferedSourceException(null);
++                }
++            } catch (final Throwable e) {
++                // FIXME: What should really be done here if conversion fails?
++
++                // Sets the buffering state
++                currentBufferedSourceException = new Buffered.SourceException(
++                        this, e);
++                markAsDirty();
++
++                // Throws the source exception
++                throw currentBufferedSourceException;
++            }
++
++            final boolean wasModified = isModified();
++            setModified(false);
++
++            // If the new value differs from the previous one
++            if (!equals(newFieldValue, getInternalValue())) {
++                setInternalValue(newFieldValue);
++                fireValueChange(false);
++            } else if (wasModified) {
++                // If the value did not change, but the modification status did
++                markAsDirty();
++            }
++        }
++    }
++
 +    /**
 +     * Gets the converter used to convert the property data source value to the
 +     * field value.
 +     * 
 +     * @return The converter or null if none is set.
 +     */
 +    public Converter<T, Object> getConverter() {
 +        return converter;
 +    }
 +
 +    /**
 +     * Sets the converter used to convert the field value to property data
 +     * source type. The converter must have a presentation type that matches the
 +     * field type.
 +     * 
 +     * @param converter
 +     *            The new converter to use.
 +     */
 +    public void setConverter(Converter<T, ?> converter) {
 +        this.converter = (Converter<T, Object>) converter;
 +        markAsDirty();
 +    }
 +
 +    @Override
 +    protected AbstractFieldState getState() {
 +        return (AbstractFieldState) super.getState();
 +    }
 +
 +    @Override
 +    public void beforeClientResponse(boolean initial) {
 +        super.beforeClientResponse(initial);
 +
 +        // Hide the error indicator if needed
 +        getState().hideErrors = shouldHideErrors();
 +    }
 +
 +    /**
 +     * Registers this as an event listener for events sent by the data source
 +     * (if any). Does nothing if
 +     * <code>isListeningToPropertyEvents == true</code>.
 +     */
 +    private void addPropertyListeners() {
 +        if (!isListeningToPropertyEvents) {
 +            if (dataSource instanceof Property.ValueChangeNotifier) {
 +                ((Property.ValueChangeNotifier) dataSource).addListener(this);
 +            }
 +            if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
 +                ((Property.ReadOnlyStatusChangeNotifier) dataSource)
 +                        .addListener(this);
 +            }
 +            isListeningToPropertyEvents = true;
 +        }
 +    }
 +
 +    /**
 +     * Stops listening to events sent by the data source (if any). Does nothing
 +     * if <code>isListeningToPropertyEvents == false</code>.
 +     */
 +    private void removePropertyListeners() {
 +        if (isListeningToPropertyEvents) {
 +            if (dataSource instanceof Property.ValueChangeNotifier) {
 +                ((Property.ValueChangeNotifier) dataSource)
 +                        .removeListener(this);
 +            }
 +            if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
 +                ((Property.ReadOnlyStatusChangeNotifier) dataSource)
 +                        .removeListener(this);
 +            }
 +            isListeningToPropertyEvents = false;
 +        }
 +    }
 +}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..aa6c536af33c088bff62d8876c341e5cb4ff6a2b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,382 @@@
++<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
++<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
++<head profile="http://selenium-ide.openqa.org/profiles/test-case">
++<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
++<link rel="selenium.base" href="" />
++<title>DateFieldExtendedRange</title>
++</head>
++<body>
++<table cellpadding="1" cellspacing="1" border="1">
++<thead>
++<tr><td rowspan="1" colspan="3">DateFieldExtendedRange</td></tr>
++</thead><tbody>
++<tr>
++      <td>open</td>
++      <td>/run/DateFieldExtendedRange?restartApplication</td>
++      <td></td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>8,16</td>
++</tr>
++<tr>
++      <td>screenCapture</td>
++      <td></td>
++      <td>2011-01-01</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>27</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]#day37</td>
++      <td>6</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>11,11</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#popupButton</td>
++      <td>10,12</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>26</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]#day36</td>
++      <td>5</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#popupButton</td>
++      <td>15,20</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#popupButton</td>
++      <td>11,8</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>27</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]#day37</td>
++      <td>6</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[2]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>27</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#day37</td>
++      <td>6</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[4]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>26</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#day36</td>
++      <td>5</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[6]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>27</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#day37</td>
++      <td>6</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>11,9</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>12,9</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#field</td>
++      <td>27.12.2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#popupButton</td>
++      <td>10,15</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>14,7</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#field</td>
++      <td>12/26/10</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#popupButton</td>
++      <td>15,10</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>6,11</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#field</td>
++      <td>27.12.2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[2]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>11,7</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>joulukuu 2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[4]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>13,4</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>December 2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//div[@id='runDateFieldExtendedRange-312092003']/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div/div[6]/div[2]/div/table/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>11,8</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>joulukuu 2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#day27</td>
++      <td>13,13</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>tammikuu 2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#day26</td>
++      <td>12,7</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>January 2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#day27</td>
++      <td>14,12</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>tammikuu 2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>14,17</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>enter</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#field</td>
++      <td>3.1.2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#popupButton</td>
++      <td>9,12</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>enter</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#field</td>
++      <td>1/2/11</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#popupButton</td>
++      <td>15,15</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>down</td>
++</tr>
++<tr>
++      <td>pressSpecialKey</td>
++      <td>vaadin=runDateFieldExtendedRange::Root/VOverlay[0]/VCalendarPanel[0]</td>
++      <td>enter</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#field</td>
++      <td>3.1.2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>10,15</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>12,8</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#field</td>
++      <td>27.12.2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#popupButton</td>
++      <td>12,15</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>14,9</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VPopupCalendar[0]#field</td>
++      <td>12/26/10</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#popupButton</td>
++      <td>15,15</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>//table[@id='PID_VAADIN_POPUPCAL']/tbody/tr[2]/td/table/tbody/tr[2]/td[2]/span</td>
++      <td>8,12</td>
++</tr>
++<tr>
++      <td>assertValue</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VPopupCalendar[0]#field</td>
++      <td>27.12.2010</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#day37</td>
++      <td>18,10</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>helmikuu 2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#day36</td>
++      <td>15,11</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>February 2011</td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#day37</td>
++      <td>19,13</td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[5]/VDateFieldCalendar[0]/VCalendarPanel[0]#header</td>
++      <td>helmikuu 2011</td>
++</tr>
++<tr>
++      <td>click</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]</td>
++      <td></td>
++</tr>
++<tr>
++      <td>mouseClick</td>
++      <td>vaadin=runDateFieldExtendedRange::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VPopupCalendar[0]#popupButton</td>
++      <td>10,13</td>
++</tr>
++<tr>
++      <td>screenCapture</td>
++      <td></td>
++      <td>2010-02-16</td>
++</tr>
++
++</tbody></table>
++</body>
++</html>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e420278148a1585168e8333ddbfd6c62ed1a76bf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,79 @@@
++package com.vaadin.tests.components.datefield;
++
++import java.util.Calendar;
++import java.util.Locale;
++
++import com.vaadin.tests.components.TestBase;
++import com.vaadin.ui.Button;
++import com.vaadin.ui.Button.ClickEvent;
++import com.vaadin.ui.Button.ClickListener;
++import com.vaadin.ui.DateField;
++import com.vaadin.ui.GridLayout;
++import com.vaadin.ui.InlineDateField;
++import com.vaadin.ui.PopupDateField;
++
++@SuppressWarnings("serial")
++public class DateFieldExtendedRange extends TestBase {
++
++    private Calendar date = Calendar.getInstance();
++
++    @Override
++    protected void setup() {
++        date.set(2011, 0, 1);
++
++        GridLayout layout = new GridLayout(2, 3);
++        layout.setWidth("600px");
++        layout.setSpacing(true);
++
++        final DateField[] fields = new DateField[6];
++
++        Locale fi = new Locale("fi", "FI");
++        Locale us = new Locale("en", "US");
++
++        fields[0] = makeDateField(true, fi, "Finnish locale");
++        fields[1] = makeDateField(false, fi, "Finnish locale");
++
++        fields[2] = makeDateField(true, us, "US English locale");
++        fields[3] = makeDateField(false, us, "US English locale");
++
++        fields[4] = makeDateField(true, fi, "Finnish locale with week numbers");
++        fields[4].setShowISOWeekNumbers(true);
++        fields[5] = makeDateField(false, fi, "Finnish locale with week numbers");
++        fields[5].setShowISOWeekNumbers(true);
++
++        for (DateField f : fields) {
++            layout.addComponent(f);
++        }
++
++        addComponent(layout);
++
++        addComponent(new Button("Change date", new ClickListener() {
++            public void buttonClick(ClickEvent event) {
++                date.set(2010, 1, 16);
++                for (DateField f : fields) {
++                    f.setValue(date.getTime());
++                }
++            }
++        }));
++    }
++
++    @Override
++    protected String getDescription() {
++        return "Show a few days of the preceding and following months in the datefield popup";
++    }
++
++    @Override
++    protected Integer getTicketNumber() {
++        return 6718;
++    }
++
++    private DateField makeDateField(boolean isPopup, Locale locale,
++            String caption) {
++        DateField df = isPopup ? new PopupDateField() : new InlineDateField();
++        df.setResolution(DateField.RESOLUTION_DAY);
++        df.setValue(date.getTime());
++        df.setLocale(locale);
++        df.setCaption(caption);
++        return df;
++    }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9bd0149bdb236375ad8faba77a45fc991f3c1821
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,67 @@@
++<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
++<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
++<head profile="http://selenium-ide.openqa.org/profiles/test-case">
++<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
++<link rel="selenium.base" href="" />
++<title>TableReduceContainerSize</title>
++</head>
++<body>
++<table cellpadding="1" cellspacing="1" border="1">
++<thead>
++<tr><td rowspan="1" colspan="3">TableReduceContainerSize</td></tr>
++</thead><tbody>
++<tr>
++      <td>open</td>
++      <td>/run/TableReduceContainerSize?restartApplication</td>
++      <td></td>
++</tr>
++<tr>
++      <td>scroll</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]</td>
++      <td>19700</td>
++</tr>
++<tr>
++      <td>pause</td>
++      <td>300</td>
++      <td></td>
++</tr>
++<tr>
++      <td>click</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[3]/VButton[0]/domChild[0]/domChild[0]</td>
++      <td></td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[1]/VLabel[0]</td>
++      <td>Index: *</td>
++</tr>
++<tr>
++      <td>click</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[3]/VButton[0]/domChild[0]/domChild[0]</td>
++      <td></td>
++</tr>
++<tr>
++      <td>scroll</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]</td>
++      <td>19700</td>
++</tr>
++<tr>
++      <td>pause</td>
++      <td>300</td>
++      <td></td>
++</tr>
++<tr>
++      <td>click</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[2]/VButton[0]/domChild[0]/domChild[0]</td>
++      <td></td>
++</tr>
++<tr>
++      <td>assertText</td>
++      <td>vaadin=runTableReduceContainerSize::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[1]/VLabel[0]</td>
++      <td>Index: *</td>
++</tr>
++
++</tbody></table>
++</body>
++</html>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bc729ef042c8eb2037bf0a91a76542a6b53ee15f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,147 @@@
++package com.vaadin.tests.components.table;
++
++import java.util.ArrayList;
++import java.util.Date;
++import java.util.List;
++
++import com.vaadin.data.Container.Filter;
++import com.vaadin.data.Item;
++import com.vaadin.data.util.BeanItemContainer;
++import com.vaadin.tests.components.TestBase;
++import com.vaadin.ui.Button;
++import com.vaadin.ui.Button.ClickEvent;
++import com.vaadin.ui.HorizontalLayout;
++import com.vaadin.ui.Label;
++import com.vaadin.ui.Table;
++
++/**
++ * Test for #8291 and #7666: NegativeArraySizeException when Table scrolled to
++ * the end and its size reduced.
++ */
++public class TableReduceContainerSize extends TestBase {
++
++    @Override
++    protected void setup() {
++        addComponent(new TestView());
++    }
++
++    private static class DecimateFilter implements Filter {
++        public boolean passesFilter(Object itemId, Item item)
++                throws UnsupportedOperationException {
++            return ((((TestObject) itemId).property3 % 10) == 0);
++        }
++
++        public boolean appliesToProperty(Object propertyId) {
++            return true;
++        }
++    }
++
++    private static class TestView extends HorizontalLayout {
++
++        private Filter filter = null;
++
++        private boolean reduceData;
++
++        private TestView() {
++            final Table table = new Table();
++            List<TestObject> data = createData(1000);
++            final BeanItemContainer<TestObject> container = new BeanItemContainer<TestObject>(
++                    TestObject.class, data) {
++
++                @Override
++                public int size() {
++                    if (reduceData) {
++                        return 100;
++                    } else {
++                        return super.size();
++                    }
++                }
++            };
++            table.setContainerDataSource(container);
++            addComponent(table);
++            final Label label = new Label();
++            addComponent(label);
++            Button button = new Button("Click");
++            button.addListener(new Button.ClickListener() {
++                public void buttonClick(ClickEvent event) {
++                    try {
++                        reduceData = !reduceData;
++                        table.refreshRowCache();
++                        label.setValue("Index: "
++                                + table.getCurrentPageFirstItemIndex());
++                    } catch (Exception e) {
++                        label.setValue("Exception: "
++                                + e.getClass().getSimpleName());
++                    }
++                }
++            });
++            addComponent(button);
++            Button button2 = new Button("Filter");
++            button2.addListener(new Button.ClickListener() {
++                public void buttonClick(ClickEvent event) {
++                    try {
++                        if (filter != null) {
++                            container.removeAllContainerFilters();
++                            filter = null;
++                        } else {
++                            filter = new DecimateFilter();
++                            container.addContainerFilter(filter);
++                        }
++                        table.refreshRowCache();
++                        label.setValue("Index: "
++                                + table.getCurrentPageFirstItemIndex());
++                    } catch (Exception e) {
++                        label.setValue("Exception: "
++                                + e.getClass().getSimpleName());
++                    }
++                }
++            });
++            addComponent(button2);
++        }
++    }
++
++    private static List<TestObject> createData(int count) {
++        ArrayList<TestObject> data = new ArrayList<TestObject>(count);
++        for (int i = 0; i < count; i++) {
++            data.add(new TestObject("string-" + i, new Date(), i));
++        }
++        return data;
++    }
++
++    public static class TestObject {
++
++        private String property1;
++        private Date property2;
++        private Integer property3;
++
++        public TestObject(String property1, Date property2, Integer property3) {
++            this.property1 = property1;
++            this.property2 = property2;
++            this.property3 = property3;
++        }
++
++        public String getProperty1() {
++            return property1;
++        }
++
++        public Date getProperty2() {
++            return property2;
++        }
++
++        public Integer getProperty3() {
++            return property3;
++        }
++
++    }
++
++    @Override
++    protected String getDescription() {
++        return "Table throws NegativeArraySizeException if container size is reduced to less than current scroll position";
++    }
++
++    @Override
++    protected Integer getTicketNumber() {
++        return 8291;
++    }
++
++}